diff --git a/src/expr.rs b/src/expr.rs index 2f230ecc3b4..25226991fbc 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -132,7 +132,7 @@ pub(crate) fn format_expr( ast::ExprKind::Tup(ref items) => { rewrite_tuple(context, items.iter(), expr.span, shape, items.len() == 1) } - ast::ExprKind::Let(..) => None, + ast::ExprKind::Let(ref pat, ref expr, _span) => rewrite_let(context, shape, pat, expr), ast::ExprKind::If(..) | ast::ExprKind::ForLoop(..) | ast::ExprKind::Loop(..) @@ -1834,6 +1834,40 @@ fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>( Some(format!("({list_str})")) } +fn rewrite_let( + context: &RewriteContext<'_>, + shape: Shape, + pat: &ast::Pat, + expr: &ast::Expr, +) -> Option { + 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 + .snippet_provider + .span_after(expr.span.with_lo(pat.span.hi()), "="); + let comments_span = mk_sp(comments_lo, expr.span.lo()); + rewrite_assign_rhs_with_comments( + context, + result, + expr, + shape, + &RhsAssignKind::Expr(&expr.kind, expr.span), + RhsTactics::Default, + comments_span, + true, + ) +} + pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>( context: &'a RewriteContext<'_>, items: impl Iterator, diff --git a/src/pairs.rs b/src/pairs.rs index 96f023b3b0e..9dac20d3699 100644 --- a/src/pairs.rs +++ b/src/pairs.rs @@ -42,9 +42,13 @@ pub(crate) fn rewrite_all_pairs( context: &RewriteContext<'_>, ) -> Option { 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, diff --git a/tests/source/let_chains.rs b/tests/source/let_chains.rs new file mode 100644 index 00000000000..88ee126bfbd --- /dev/null +++ b/tests/source/let_chains.rs @@ -0,0 +1,121 @@ +fn main() { + if let x = x && x {} + + if xxx && let x = x {} + + if aaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaa && aaaaaaaaa && let Some(x) = xxxxxxxxxxxx && aaaaaaa && let None = aaaaaaaaaa {} + + if aaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaa || aaaaaaaaa && let Some(x) = xxxxxxxxxxxx && aaaaaaa && let None = aaaaaaaaaa {} + + if let Some(Struct { x:TS(1,2) }) = path::to::<_>(hehe) + && let [Simple, people] = /* get ready */ create_universe(/* hi */ GreatPowers).initialize_badminton().populate_swamps() && + let everybody = (Loops { hi /*hi*/ , ..loopy() }) || summons::triumphantly() { todo!() } + + 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 { + + } +} diff --git a/tests/source/match.rs b/tests/source/match.rs index b5dc9957a2c..d1d8d7f2c36 100644 --- a/tests/source/match.rs +++ b/tests/source/match.rs @@ -292,6 +292,9 @@ fn guards() { aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if fooooooooooooooooooooo && (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb || cccccccccccccccccccccccccccccccccccccccc) => {} + Hi { friend } if let None = friend => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if let Some(foooooooooooooo) = hiiiiiiiiiiiiiii => {} + aaaaaaaaaaaaaaaaa if let Superman { powers: Some(goteem), .. } = all::get_random_being::() => {} } } diff --git a/tests/target/let_chains.rs b/tests/target/let_chains.rs new file mode 100644 index 00000000000..165641521cf --- /dev/null +++ b/tests/target/let_chains.rs @@ -0,0 +1,128 @@ +fn main() { + if let x = x + && x + {} + + if xxx && let x = x {} + + if aaaaaaaaaaaaaaaaaaaaa + && aaaaaaaaaaaaaaa + && aaaaaaaaa + && let Some(x) = xxxxxxxxxxxx + && aaaaaaa + && let None = aaaaaaaaaa + {} + + if aaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaa + || aaaaaaaaa + && let Some(x) = xxxxxxxxxxxx + && aaaaaaa + && let None = aaaaaaaaaa + {} + + if let Some(Struct { x: TS(1, 2) }) = path::to::<_>(hehe) + && let [Simple, people] = /* get ready */ + create_universe(/* hi */ GreatPowers) + .initialize_badminton() + .populate_swamps() + && let everybody = (Loops { + hi, /*hi*/ + ..loopy() + }) + || summons::triumphantly() + { + todo!() + } + + 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 + {} +} diff --git a/tests/target/match.rs b/tests/target/match.rs index 1bf3fb758ee..0e7815a814d 100644 --- a/tests/target/match.rs +++ b/tests/target/match.rs @@ -317,6 +317,14 @@ fn guards() { if fooooooooooooooooooooo && (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb || cccccccccccccccccccccccccccccccccccccccc) => {} + Hi { friend } if let None = friend => {} + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + if let Some(foooooooooooooo) = hiiiiiiiiiiiiiii => {} + aaaaaaaaaaaaaaaaa + if let Superman { + powers: Some(goteem), + .. + } = all::get_random_being::() => {} } }