Skip to content
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
38 changes: 30 additions & 8 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1660,14 +1660,15 @@ pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) ->
result
}

pub(crate) fn rewrite_paren(
/// Remove any unnecessary nested parentheses from the expression. Parentheses are unnecessary when
/// they contain only whitespace.
pub(crate) fn unwrap_parens<'a, R: Rewrite + Spanned>(
context: &RewriteContext<'_>,
mut subexpr: &ast::Expr,
mut subexpr: &'a R,
shape: Shape,
mut span: Span,
) -> RewriteResult {
debug!("rewrite_paren, shape: {:?}", shape);

nested_getter: fn(&R) -> Option<&R>,
) -> Result<(&'a R, Span, Span, String, String), RewriteError> {
// Extract comments within parens.
let mut pre_span;
let mut post_span;
Expand All @@ -1677,21 +1678,42 @@ pub(crate) fn rewrite_paren(
loop {
// 1 = "(" or ")"
pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span().lo());
post_span = mk_sp(subexpr.span.hi(), span.hi() - BytePos(1));
post_span = mk_sp(subexpr.span().hi(), span.hi() - BytePos(1));
pre_comment = rewrite_missing_comment(pre_span, shape, context)?;
post_comment = rewrite_missing_comment(post_span, shape, context)?;

// Remove nested parens if there are no comments.
if let ast::ExprKind::Paren(ref subsubexpr) = subexpr.kind {
if let Some(subsubexpr) = nested_getter(&subexpr) {
if remove_nested_parens && pre_comment.is_empty() && post_comment.is_empty() {
span = subexpr.span;
span = subexpr.span();
subexpr = subsubexpr;
continue;
}
}

break;
}
Ok((subexpr, pre_span, post_span, pre_comment, post_comment))
}

pub(crate) fn rewrite_paren(
context: &RewriteContext<'_>,
subexpr: &ast::Expr,
shape: Shape,
span: Span,
) -> RewriteResult {
debug!("rewrite_paren, shape: {:?}", shape);

let (subexpr, pre_span, post_span, pre_comment, post_comment) = unwrap_parens(
context,
subexpr,
shape,
span,
|expr: &ast::Expr| match &expr.kind {
ast::ExprKind::Paren(subsubexpr) => Some(subsubexpr),
_ => None,
},
)?;

// 1 = `(` and `)`
let sub_shape = shape.offset_left(1, span)?.sub_width(1, span)?;
Expand Down
13 changes: 13 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::config::lists::*;
use crate::config::{IndentStyle, StyleEdition, TypeDensity};
use crate::expr::{
ExprType, RhsAssignKind, format_expr, rewrite_assign_rhs, rewrite_tuple, rewrite_unary_prefix,
unwrap_parens,
};
use crate::lists::{
ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
Expand Down Expand Up @@ -928,6 +929,18 @@ impl Rewrite for ast::Ty {
.rewrite_result(context, Shape::legacy(budget, shape.indent + 1))
.map(|ty_str| format!("({})", ty_str));
}
let ty = if context.config.style_edition() < StyleEdition::Edition2027 {
ty
} else {
let (ty, _, _, _, _) =
unwrap_parens(context, ty.as_ref(), shape, self.span, |ty: &ast::Ty| {
match &ty.kind {
ast::TyKind::Paren(nested) => Some(nested),
_ => None,
}
})?;
ty
};

// 2 = ()
if let Some(sh) = shape.sub_width_opt(2) {
Expand Down
7 changes: 7 additions & 0 deletions tests/source/struct_fields.rs

@ytmimi ytmimi Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, if this doesn't already it should probably also be configured using remove_nested_parens. We'll want to add a test case to show that parens aren't removed when remove_nested_parens=false. It's true by default so it's not necessary in this case, but might be good to be explicit about it.

I think we should also add a style_edition=2024 test case to show that parens aren't removed even with remove_nested_parens=true to highlight the issue we're solving.

View changes since the review

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// rustfmt-style_edition: 2027

struct F {
f: ((u32)),

@ytmimi ytmimi Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are parens removed if there are comments?

View changes since the review

@ytmimi ytmimi Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should probably also add a test case with a lot more parens. Something like f: (((((u32))))), to make sure it's idempotent and doesn't just remove one set of parens every time you run rustfmt.

@matthewhughes934 matthewhughes934 Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Are parens removed if there are comments?

only parens without any leading/trailing comments, e.g.

struct S {
    f: (/* comment */((u8))),
}

Would be re-written to:

struct S {
    f: ((u8)),
}

Notably:

  1. The comment is still lost (since this change doesn't resolve the original issue)

  2. This causes a non-idempotent format, since

    struct S {
        f: ((u8)),
    }

    is then formatted to

    struct S {
        f: (u8),
    }

So I think this shouldn't be a standalone change, and I'll need to just combine it with my change to not remove those comments (I created this PR to try and split that work into a few smaller changes)

}

struct F(((u32)));
7 changes: 7 additions & 0 deletions tests/target/struct_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// rustfmt-style_edition: 2027

struct F {
f: (u32),
}

struct F((u32));
Loading