Rewrite match expressions

This commit is contained in:
Nick Cameron 2015-08-14 20:00:22 +12:00
parent 8dbe2133fb
commit dc2544712c
4 changed files with 245 additions and 1 deletions

View File

@ -12,7 +12,7 @@ use rewrite::{Rewrite, RewriteContext};
use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
use string::{StringFormat, rewrite_string};
use StructLitStyle;
use utils::{span_after, make_indent, extra_offset};
use utils::{span_after, make_indent, extra_offset, first_line_width, last_line_width};
use visitor::FmtVisitor;
use config::BlockIndentStyle;
use comment::{FindUncommented, rewrite_comment};
@ -99,6 +99,9 @@ impl Rewrite for ast::Expr {
width,
offset)
}
ast::Expr_::ExprMatch(ref cond, ref arms, _) => {
rewrite_match(context, cond, arms, width, offset)
}
ast::Expr_::ExprPath(ref qself, ref path) => {
rewrite_path(context, qself.as_ref(), path, width, offset)
}
@ -303,6 +306,175 @@ fn rewrite_if_else(context: &RewriteContext,
Some(result)
}
fn rewrite_match(context: &RewriteContext,
cond: &ast::Expr,
arms: &[ast::Arm],
width: usize,
offset: usize)
-> Option<String> {
// TODO comments etc. (I am somewhat surprised we don't need handling for these).
// `match `cond` {`
let cond_str = try_opt!(cond.rewrite(context, width - 8, offset + 6));
let mut result = format!("match {} {{", cond_str);
let block_indent = context.block_indent;
let nested_context = context.nested_context();
let arm_indent_str = make_indent(nested_context.block_indent);
for arm in arms {
result.push('\n');
result.push_str(&arm_indent_str);
result.push_str(&&try_opt!(arm.rewrite(&nested_context,
context.config.max_width -
nested_context.block_indent,
nested_context.block_indent)));
}
result.push('\n');
result.push_str(&make_indent(block_indent));
result.push('}');
Some(result)
}
// Match arms.
impl Rewrite for ast::Arm {
fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
let &ast::Arm { ref attrs, ref pats, ref guard, ref body } = self;
let indent_str = make_indent(offset);
// TODO attrs
// Patterns
let pat_strs = pats.iter().map(|p| p.rewrite(context,
// 5 = ` => {`
width - 5,
offset + context.config.tab_spaces)).collect::<Vec<_>>();
if pat_strs.iter().any(|p| p.is_none()) {
return None;
}
let pat_strs = pat_strs.into_iter().map(|p| p.unwrap()).collect::<Vec<_>>();
let mut total_width = pat_strs.iter().fold(0, |a, p| a + p.len());
// Add ` | `.len().
total_width += (pat_strs.len() - 1) * 3;
let mut vertical = total_width > width - 5 || pat_strs.iter().any(|p| p.contains('\n'));
if !vertical {
// If the patterns were previously stacked, keep them stacked.
// FIXME should be an option.
let pat_span = mk_sp(pats[0].span.lo, pats[pats.len() - 1].span.hi);
let pat_str = context.codemap.span_to_snippet(pat_span).unwrap();
vertical = pat_str.find('\n').is_some();
}
let pats_width = if vertical {
pat_strs[pat_strs.len() - 1].len()
} else {
total_width
};
let mut pats_str = String::new();
for p in pat_strs {
if pats_str.len() > 0 {
if vertical {
pats_str.push_str(" |\n");
pats_str.push_str(&indent_str);
} else {
pats_str.push_str(" | ");
}
}
pats_str.push_str(&p);
}
// TODO probably want to compute the guard width first, then the rest
// TODO also, only subtract the guard width from the last pattern.
// If guard.
let guard_str = try_opt!(rewrite_guard(context, guard, width, offset, pats_width));
let pats_str = format!("{}{}", pats_str, guard_str);
// Where the next text can start.
let mut line_start = last_line_width(&pats_str);
if pats_str.find('\n').is_none() {
line_start += offset;
}
let comma = if let ast::ExprBlock(_) = body.node {
String::new()
} else {
",".to_owned()
};
// Let's try and get the arm body on the same line as the condition.
// 4 = ` => `.len()
if context.config.max_width > line_start + comma.len() + 4 {
let budget = context.config.max_width - line_start - comma.len() - 4;
if let Some(ref body_str) = body.rewrite(context,
budget,
offset + context.config.tab_spaces) {
if first_line_width(body_str) <= budget {
return Some(format!("{} => {}{}", pats_str, body_str, comma));
}
}
}
// We have to push the body to the next line.
if comma.len() > 0 {
// We're trying to fit a block in, but it still failed, give up.
return None;
}
let body_str = try_opt!(body.rewrite(context,
width - context.config.tab_spaces,
offset + context.config.tab_spaces));
Some(format!("{} =>\n{}{}",
pats_str,
make_indent(offset + context.config.tab_spaces),
body_str))
}
}
// The `if ...` guard on a match arm.
fn rewrite_guard(context: &RewriteContext,
guard: &Option<ptr::P<ast::Expr>>,
width: usize,
offset: usize,
// The amount of space used up on this line for the pattern in
// the arm.
pattern_width: usize)
-> Option<String> {
if let &Some(ref guard) = guard {
// 4 = ` if `, 5 = ` => {`
let overhead = pattern_width + 4 + 5;
if overhead < width {
let cond_str = guard.rewrite(context,
width - overhead,
offset + context.config.tab_spaces);
if let Some(cond_str) = cond_str {
return Some(format!(" if {}", cond_str));
}
}
// Not enough space to put the guard after the pattern, try a newline.
let overhead = context.config.tab_spaces + 4 + 5;
if overhead < width {
let cond_str = guard.rewrite(context,
width - overhead,
offset + context.config.tab_spaces);
if let Some(cond_str) = cond_str {
return Some(format!("\n{}if {}",
make_indent(offset + context.config.tab_spaces),
cond_str));
}
}
None
} else {
Some(String::new())
}
}
fn rewrite_pat_expr(context: &RewriteContext,
pat: Option<&ast::Pat>,
expr: &ast::Expr,

View File

@ -30,3 +30,13 @@ pub struct RewriteContext<'a> {
pub config: &'a Config,
pub block_indent: usize,
}
impl<'a> RewriteContext<'a> {
pub fn nested_context(&self) -> RewriteContext<'a> {
RewriteContext {
codemap: self.codemap,
config: self.config,
block_indent: self.block_indent + self.config.tab_spaces,
}
}
}

View File

@ -82,6 +82,25 @@ pub fn format_mutability(mutability: ast::Mutability) -> &'static str {
}
}
// The width of the first line in s.
#[inline]
pub fn first_line_width(s: &str) -> usize {
match s.find('\n') {
Some(n) => n,
None => s.len(),
}
}
// The width of the last line in s.
#[inline]
pub fn last_line_width(s: &str) -> usize {
match s.rfind('\n') {
Some(n) => s.len() - n,
None => s.len(),
}
}
#[inline]
fn is_skip(meta_item: &MetaItem) -> bool {
match meta_item.node {
MetaItem_::MetaWord(ref s) => *s == SKIP_ANNOTATION,
@ -95,6 +114,7 @@ pub fn contains_skip(attrs: &[Attribute]) -> bool {
}
// Find the end of a TyParam
#[inline]
pub fn end_typaram(typaram: &ast::TyParam) -> BytePos {
typaram.bounds.last().map(|bound| match *bound {
ast::RegionTyParamBound(ref lt) => lt.span,

42
tests/target/match.rs Normal file
View File

@ -0,0 +1,42 @@
// Match expressions.
fn foo() {
// A match expression.
match x {
// Some comment.
a => foo(),
b if 0 < 42 => foo(),
c => { // Another comment.
// Comment.
an_expression;
foo()
}
// Perhaps this should introduce braces?
Foo(ref bar) =>
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
Pattern1 | Pattern2 | Pattern3 => false,
Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn |
Paternnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn => {
blah
}
Patternnnnnnnnnnnnnnnnnnn |
Patternnnnnnnnnnnnnnnnnnn |
Patternnnnnnnnnnnnnnnnnnn |
Patternnnnnnnnnnnnnnnnnnn => meh,
Patternnnnnnnnnnnnnnnnnnn |
Patternnnnnnnnnnnnnnnnnnn if looooooooooooooooooong_guard => meh,
Patternnnnnnnnnnnnnnnnnnnnnnnnn |
Patternnnnnnnnnnnnnnnnnnnnnnnnn
if looooooooooooooooooooooooooooooooooooooooong_guard => meh,
_ => {}
}
let whatever = match something {
/// DOC COMMENT!
Some(_) => 42,
#[an_attribute]
None => 0,
};
}