implement sinlge line let-chain rules

for now, let-chains can only be formatted on a single line if the chain
consits of 2 expressions where the first is an identifier proceeded by
any number of unary operators and the second is a let-expr.
This commit is contained in:
Yacin Tmimi 2023-08-07 12:57:20 -04:00 committed by Caleb Cartwright
parent 457dc79a35
commit 547577fa5d
4 changed files with 236 additions and 6 deletions

View File

@ -1842,11 +1842,14 @@ fn rewrite_let(
) -> Option<String> {
let mut result = "let ".to_owned();
// TODO(ytmimi) comments could appear between `let` and the `pat`
// 4 = "let ".len()
let pat_shape = shape.offset_left(4)?;
let pat_str = pat.rewrite(context, pat_shape)?;
result.push_str(&pat_str);
// TODO(ytmimi) comments could appear between `pat` and `=`
result.push_str(" =");
let comments_lo = context

View File

@ -42,9 +42,13 @@ pub(crate) fn rewrite_all_pairs(
context: &RewriteContext<'_>,
) -> Option<String> {
expr.flatten(context, shape).and_then(|list| {
// First we try formatting on one line.
rewrite_pairs_one_line(&list, shape, context)
.or_else(|| rewrite_pairs_multiline(&list, shape, context))
if list.let_chain_count() > 0 && !list.can_rewrite_let_chain_single_line() {
rewrite_pairs_multiline(&list, shape, context)
} else {
// First we try formatting on one line.
rewrite_pairs_one_line(&list, shape, context)
.or_else(|| rewrite_pairs_multiline(&list, shape, context))
}
})
}
@ -255,6 +259,37 @@ struct PairList<'a, 'b, T: Rewrite> {
separators: Vec<&'a str>,
}
fn is_ident(expr: &ast::Expr) -> bool {
match &expr.kind {
ast::ExprKind::Path(None, path) if path.segments.len() == 1 => true,
ast::ExprKind::Unary(_, expr)
| ast::ExprKind::AddrOf(_, _, expr)
| ast::ExprKind::Paren(expr)
| ast::ExprKind::Try(expr) => is_ident(expr),
_ => false,
}
}
impl<'a, 'b> PairList<'a, 'b, ast::Expr> {
fn let_chain_count(&self) -> usize {
self.list
.iter()
.filter(|(expr, _)| matches!(expr.kind, ast::ExprKind::Let(_, _, _)))
.count()
}
fn can_rewrite_let_chain_single_line(&self) -> bool {
if self.list.len() != 2 {
return false;
}
let fist_item_is_ident = is_ident(self.list[0].0);
let second_item_is_let_chain = matches!(self.list[1].0.kind, ast::ExprKind::Let(_, _, _));
fist_item_is_ident && second_item_is_let_chain
}
}
impl FlattenPair for ast::Expr {
fn flatten(
&self,

View File

@ -13,4 +13,109 @@ fn main() {
if let XXXXXXXXX { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyy, zzzzzzzzzzzzz} = xxxxxxx()
&& let Foo = bar() { todo!() }
}
}
fn test_single_line_let_chain() {
// first item in let-chain is an ident
if a && let Some(b) = foo() {
}
// first item in let-chain is a unary ! with an ident
let unary_not = if !from_hir_call
&& let Some(p) = parent
{
};
// first item in let-chain is a unary * with an ident
let unary_deref = if *some_deref
&& let Some(p) = parent
{
};
// first item in let-chain is a unary - (neg) with an ident
let unary_neg = if -some_ident
&& let Some(p) = parent
{
};
// first item in let-chain is a try (?) with an ident
let try_ = if some_try?
&& let Some(p) = parent
{
};
// first item in let-chain is an ident wrapped in parens
let in_parens = if (some_ident)
&& let Some(p) = parent
{
};
// first item in let-chain is a ref & with an ident
let _ref = if &some_ref
&& let Some(p) = parent
{
};
// first item in let-chain is a ref &mut with an ident
let mut_ref = if &mut some_ref
&& let Some(p) = parent
{
};
// chain unary ref and try
let chain_of_unary_ref_and_try = if !&*some_ref?
&& let Some(p) = parent {
};
}
fn test_multi_line_let_chain() {
// Can only single line the let-chain if the first item is an ident
if let Some(x) = y && a {
}
// More than one let-chain must be formatted on multiple lines
if let Some(x) = y && let Some(a) = b {
}
// The ident isn't long enough so we don't wrap the first let-chain
if a && let Some(x) = y && let Some(a) = b {
}
// The ident is long enough so both let-chains are wrapped
if aaa && let Some(x) = y && let Some(a) = b {
}
// function call
if a() && let Some(x) = y {
}
// bool literal
if true && let Some(x) = y {
}
// cast to a bool
if 1 as bool && let Some(x) = y {
}
// matches! macro call
if matches!(a, some_type) && let Some(x) = y {
}
// block expression returning bool
if { true } && let Some(x) = y {
}
// field access
if a.x && let Some(x) = y {
}
}

View File

@ -1,5 +1,7 @@
fn main() {
if let x = x && x {}
if let x = x
&& x
{}
if xxx && let x = x {}
@ -12,7 +14,10 @@ fn main() {
{}
if aaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaa
|| aaaaaaaaa && let Some(x) = xxxxxxxxxxxx && aaaaaaa && let None = aaaaaaaaaa
|| aaaaaaaaa
&& let Some(x) = xxxxxxxxxxxx
&& aaaaaaa
&& let None = aaaaaaaaaa
{}
if let Some(Struct { x: TS(1, 2) }) = path::to::<_>(hehe)
@ -39,3 +44,85 @@ fn main() {
todo!()
}
}
fn test_single_line_let_chain() {
// first item in let-chain is an ident
if a && let Some(b) = foo() {}
// first item in let-chain is a unary ! with an ident
let unary_not = if !from_hir_call && let Some(p) = parent {};
// first item in let-chain is a unary * with an ident
let unary_deref = if *some_deref && let Some(p) = parent {};
// first item in let-chain is a unary - (neg) with an ident
let unary_neg = if -some_ident && let Some(p) = parent {};
// first item in let-chain is a try (?) with an ident
let try_ = if some_try? && let Some(p) = parent {};
// first item in let-chain is an ident wrapped in parens
let in_parens = if (some_ident) && let Some(p) = parent {};
// first item in let-chain is a ref & with an ident
let _ref = if &some_ref && let Some(p) = parent {};
// first item in let-chain is a ref &mut with an ident
let mut_ref = if &mut some_ref && let Some(p) = parent {};
// chain unary ref and try
let chain_of_unary_ref_and_try = if !&*some_ref? && let Some(p) = parent {};
}
fn test_multi_line_let_chain() {
// Can only single line the let-chain if the first item is an ident
if let Some(x) = y
&& a
{}
// More than one let-chain must be formatted on multiple lines
if let Some(x) = y
&& let Some(a) = b
{}
// The ident isn't long enough so we don't wrap the first let-chain
if a && let Some(x) = y
&& let Some(a) = b
{}
// The ident is long enough so both let-chains are wrapped
if aaa
&& let Some(x) = y
&& let Some(a) = b
{}
// function call
if a()
&& let Some(x) = y
{}
// bool literal
if true
&& let Some(x) = y
{}
// cast to a bool
if 1 as bool
&& let Some(x) = y
{}
// matches! macro call
if matches!(a, some_type)
&& let Some(x) = y
{}
// block expression returning bool
if { true }
&& let Some(x) = y
{}
// field access
if a.x
&& let Some(x) = y
{}
}