Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement remove_nested_blocks option #6489

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
35 changes: 35 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,41 @@ fn main() {
}
```

## `remove_nested_blocks`

Works similarly to [`remove_nested_parens`](#remove_nested_parens), but removes nested blocks.

- **Default value**: `false`,
- **Possible values**: `true`, `false`
- **Stable**: No

Blocks with any sort of comments or attributes, `unsafe` and `const` blocks will not be removed.

#### `false` (default):
```rust
fn main() {
{
{
// comment
{
foo();
}
}
}
}
```

#### `true`:
```rust
fn main() {
{
// comment
{
foo();
}
}
}
```

## `reorder_impl_items`

Expand Down
3 changes: 3 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ create_config! {

// Misc.
remove_nested_parens: RemoveNestedParens, true, "Remove nested parens";
remove_nested_blocks: RemoveNestedBlocks, false, "Remove nested blocks";
combine_control_expr: CombineControlExpr, false, "Combine control expressions with function \
calls";
short_array_element_width_threshold: ShortArrayElementWidthThreshold, true,
Expand Down Expand Up @@ -796,6 +797,7 @@ space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
remove_nested_blocks = false
combine_control_expr = true
short_array_element_width_threshold = 10
overflow_delimited_expr = false
Expand Down Expand Up @@ -887,6 +889,7 @@ space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
remove_nested_blocks = false
combine_control_expr = true
short_array_element_width_threshold = 10
overflow_delimited_expr = true
Expand Down
1 change: 1 addition & 0 deletions src/config/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ config_option_with_style_edition_default!(

// Misc.
RemoveNestedParens, bool, _ => true;
RemoveNestedBlocks, bool, _ => false;
CombineControlExpr, bool, _ => true;
ShortArrayElementWidthThreshold, usize, _ => 10;
OverflowDelimitedExpr, bool, Edition2024 => true, _ => false;
Expand Down
102 changes: 94 additions & 8 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,15 @@ pub(crate) fn format_expr(
// not the `ast::Block` node we're about to rewrite. To prevent dropping inner
// attributes call `rewrite_block` directly.
// See https://github.com/rust-lang/rustfmt/issues/6158
rewrite_block(block, Some(&expr.attrs), opt_label, context, shape)?
rewrite_block_inner(
block,
Some(&expr.attrs),
opt_label,
true,
context,
shape,
false,
)?
}
_ => anon_const.rewrite_result(context, shape)?,
};
Expand All @@ -192,14 +200,12 @@ pub(crate) fn format_expr(
// Rewrite block without trying to put it in a single line.
Ok(rw)
} else {
let prefix = block_prefix(context, block, shape)?;

rewrite_block_with_visitor(
context,
&prefix,
rewrite_block_inner(
block,
Some(&expr.attrs),
opt_label,
false,
context,
shape,
true,
)
Expand Down Expand Up @@ -631,7 +637,35 @@ fn rewrite_block(
context: &RewriteContext<'_>,
shape: Shape,
) -> RewriteResult {
rewrite_block_inner(block, attrs, label, true, context, shape)
rewrite_block_inner(block, attrs, label, true, context, shape, true)
}

fn remove_nested_block(
block: &ast::Block,
inner_label: Option<ast::Label>,
block_expr: &ast::Expr,
inner_block: &ast::Block,
context: &RewriteContext<'_>,
shape: Shape,
can_inner_be_removed: bool,
) -> Option<RewriteResult> {
let pre_inner_block_span = mk_sp(block.span.lo(), block_expr.span.lo());
let post_inner_block_span = mk_sp(block_expr.span.hi(), block.span.hi());

let immdiately_contains_comment = contains_comment(context.snippet(pre_inner_block_span))
|| contains_comment(context.snippet(post_inner_block_span));
if immdiately_contains_comment {
return None;
}
Some(rewrite_block_inner(
inner_block,
Some(&block_expr.attrs),
inner_label,
true,
context,
shape,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is correct, it does seem to work, but I'm confused what the shape is even for.

can_inner_be_removed,
))
}

fn rewrite_block_inner(
Expand All @@ -641,9 +675,61 @@ fn rewrite_block_inner(
allow_single_line: bool,
context: &RewriteContext<'_>,
shape: Shape,
can_be_removed: bool,
// ^ this is a fix for const blocks, which are not removed, but are passed identically to blocks
) -> RewriteResult {
debug!("rewrite_block : {:?}", context.snippet(block.span));
let prefix = block_prefix(context, block, shape)?;

let no_attrs = attrs.is_none() || attrs.unwrap().is_empty();

// If the option `remove_nested_blocks` is enabled, we remove all unnecessary nested blocks.
// Blocks with any sort of comments or attributes, unsafe and const blocks will not be removed.
if context.config.remove_nested_blocks()
&& can_be_removed
&& prefix.is_empty()
&& !is_unsafe_block(block)
&& no_attrs
&& label.is_none()
&& block.stmts.len() == 1
{
if let ast::StmtKind::Expr(ref block_expr) = &block.stmts[0].kind {
match block_expr.kind {
ast::ExprKind::Block(ref inner_block, inner_label) => {
if let Some(rw) = remove_nested_block(
block,
inner_label,
block_expr,
inner_block,
context,
shape,
true,
) {
return rw;
}
}

ast::ExprKind::ConstBlock(ref anon_const) => {
if let ast::ExprKind::Block(ref inner_block, inner_label) =
anon_const.value.kind
{
if let Some(rw) = remove_nested_block(
block,
inner_label,
block_expr,
inner_block,
context,
shape,
false,
) {
return Ok(format!("const {}", rw?));
}
}
}
_ => {}
}
}
}
// shape.width is used only for the single line case: either the empty block `{}`,
// or an unsafe expression `unsafe { e }`.
if let Some(rw_str) = rewrite_empty_block(context, block, attrs, label, &prefix, shape) {
Expand All @@ -668,7 +754,7 @@ pub(crate) fn rewrite_let_else_block(
context: &RewriteContext<'_>,
shape: Shape,
) -> RewriteResult {
rewrite_block_inner(block, None, None, allow_single_line, context, shape)
rewrite_block_inner(block, None, None, allow_single_line, context, shape, false)
}

// Rewrite condition if the given expression has one.
Expand Down
102 changes: 102 additions & 0 deletions tests/source/remove_nested_blocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// rustfmt-remove_nested_blocks: true
fn foo() {}

// The way it is implemented right now, only 'naked' blocks are removed.
// This means unsafe blocks, const blocks, blocks with labels and attributes,
// blocks belonging to other constructs such as loops are not removed.
fn main() {
{
{
{
foo();
}
}
}
// The next block will be removed
{
{
// Blocks with comments are not removed
{
{
/* second comment */
{
foo();
}
}
}
}
}
{
{
/*
* multi-line comment
*/
foo();
}
}
{{{{{{{foo();}}}}}}}
{{/*comment*/{{{foo();}}}}}
{{/*comment*/{{/*comment*/{foo();}}}}}
{
const { const {} }
}
const { const {} }
unsafe { unsafe {} }
// As const and unsafe blocks are not 'naked' blocks, they are not removed.
unsafe {
unsafe {
{
const {
const {
{
foo();
}
}
}
}
}
}
const {
unsafe {
{
unsafe {
const {
{
foo();
}
}
}
}
}
}
{
'label1: {
'label2: {
foo();
}
}
}
'outer: loop {
{
'inner: loop {
{
foo();
}
}
}
}
if let Some(x) = 5f64.map(|x| Ok(x)) {
{
if let Some(x) = 5f64.map(|x| Ok(x)) {
foo();
}
}
}
if false {
{ {} }
}
#[cfg(debug)]
{
{ {} }
}
}
Loading
Loading