Skip to content

Commit

Permalink
implement sinlge line let-chain rules
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ytmimi authored and calebcartwright committed Oct 11, 2023
1 parent 457dc79 commit 547577f
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 6 deletions.
3 changes: 3 additions & 0 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 38 additions & 3 deletions src/pairs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
})
}

Expand Down Expand Up @@ -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,
Expand Down
107 changes: 106 additions & 1 deletion tests/source/let_chains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

}
}
91 changes: 89 additions & 2 deletions tests/target/let_chains.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
fn main() {
if let x = x && x {}
if let x = x
&& x
{}

if xxx && let x = x {}

Expand All @@ -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)
Expand All @@ -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
{}
}

0 comments on commit 547577f

Please sign in to comment.