Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/chains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,11 @@ fn should_add_parens(expr: &ast::Expr, context: &RewriteContext<'_>) -> bool {
ast::ExprKind::Closure(ref cl) => match cl.body.kind {
ast::ExprKind::Range(_, _, ast::RangeLimits::HalfOpen) => true,
ast::ExprKind::Lit(ref lit) => crate::expr::lit_ends_in_dot(lit, context),
// Closures with block bodies need parens when followed by method calls,
// otherwise the chain formatter can incorrectly associate the method call
// with the inner expression instead of the closure call result.
// See: https://github.com/rust-lang/rustfmt/issues/4050
ast::ExprKind::Block(..) => true,
_ => false,
},
_ => false,
Expand Down
18 changes: 17 additions & 1 deletion src/closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,29 @@ fn rewrite_closure_expr(
}
}

// Check if the expression is a chain that starts with a block call,
// e.g. `{ ... }().method()`. In this case the body of the closure is a
// method call chain on a block expression, which should be allowed to
// span multiple lines without being wrapped in a block.
// See: https://github.com/rust-lang/rustfmt/issues/4050
fn is_chain_on_block_call(expr: &ast::Expr) -> bool {
match expr.kind {
ast::ExprKind::MethodCall(ref call) => is_chain_on_block_call(&call.receiver),
ast::ExprKind::Field(ref subexpr, _) => is_chain_on_block_call(subexpr),
ast::ExprKind::Call(ref callee, _) => {
matches!(callee.kind, ast::ExprKind::Block(..))
}
_ => false,
}
}

// When rewriting closure's body without block, we require it to fit in a single line
// unless it is a block-like expression or we are inside macro call.
let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro())
|| context.config.force_multiline_blocks();
expr.rewrite_result(context, shape)
.and_then(|rw| {
if veto_multiline && rw.contains('\n') {
if veto_multiline && rw.contains('\n') && !is_chain_on_block_call(expr) {
Err(RewriteError::Unknown)
} else {
Ok(rw)
Expand Down
22 changes: 22 additions & 0 deletions tests/source/issue-4050.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// https://github.com/rust-lang/rustfmt/issues/4050
// Closures with block body incorrectly formatted when followed by call and method chain.

fn main() {
// Simple block with call and method chain
let a = || { 42 }().to_string();

// Multi-statement block with call and method chain
let b = || {
println!();
|| 0
}().to_string();

// Block without call, with method chain
let c = || { 42 }.to_string();

// Nested closure block with call and method chain
let d = || {
println!("hello");
42
}().to_string();
}
24 changes: 24 additions & 0 deletions tests/target/issue-4050.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// https://github.com/rust-lang/rustfmt/issues/4050
// Closures with block body incorrectly formatted when followed by call and method chain.

fn main() {
// Simple block with call and method chain
let a = || { 42 }().to_string();

// Multi-statement block with call and method chain
let b = || {
println!();
|| 0
}()
.to_string();

// Block without call, with method chain
let c = || { 42 }.to_string();

// Nested closure block with call and method chain
let d = || {
println!("hello");
42
}()
.to_string();
}
Loading