Skip to content
Open
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
57 changes: 57 additions & 0 deletions src/config/config_type.rs
Comment thread
jieyouxu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,63 @@ macro_rules! create_config {
)+
}

/// Reduces the maximum width of the configuration.
///
/// This method is intended to be used when creating a forked
/// configuration for a particular formatting context in which the
/// total available width needs to be reduced. This reduces the
/// size of max_width, and all other properties affected by small
/// heuristics, without treating those adjustments as overrides.
///
/// As an example use case, this method is used when formatting
/// arms within a declarative macro.
#[allow(unreachable_pub)]
pub fn reduce_max_width(&mut self, delta: usize) {
self.adjust_max_width(-1 * delta as isize);
Copy link
Copy Markdown
Member

@jieyouxu jieyouxu May 30, 2026

Choose a reason for hiding this comment

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

Remark: in theory, if we want to be very careful around handling of large widths, this has a potential problem where delta's usize -> isize cast can wraparound for sufficiently large usize, namely [isize::MAX + 1, usize::MAX].

However, I don't think this is a blocking concern for this PR and should not be something that's run into super frequently in practice (esp. if host pointer width is 64+). AFAICT in other parts of the code base we are also not super careful about large widths, so even if this wraps around there are other parts of the codebase that will have similar problems, AFAIK.

}

/// Increases the maximum width of the configuration.
///
/// This method is intended to be used when creating a forked
/// configuration for a particular formatting context in which the
/// total available width needs to be reduced. This reduces the
/// size of max_width, and all other properties affected by small
/// heuristics, without treating those adjustments as overrides.
///
/// As an example use case, this method is used when formatting
/// arms within a declarative macro.
#[allow(unreachable_pub)]
pub fn increase_max_width(&mut self, delta: usize) {
self.adjust_max_width(delta as isize);
}

/// Adjusts the maximum width of the configuration.
///
/// This method is intended to be used when creating a forked
/// configuration for a particular formatting context in which the
/// total available width needs to be reduced. This reduces the
/// size of max_width, and all other properties affected by small
/// heuristics, without treating those adjustments as overrides.
///
/// As an example use case, this method is used when formatting
/// arms within a declarative macro.
fn adjust_max_width(&mut self, delta: isize) {
let adjust = |value: usize| (value as isize + delta) as usize;

self.array_width.2 = adjust(self.array_width.2);
self.attr_fn_like_width.2 = adjust(self.attr_fn_like_width.2);
self.chain_width.2 = adjust(self.chain_width.2);
self.fn_call_width.2 = adjust(self.fn_call_width.2);
self.single_line_if_else_max_width.2 = adjust(self.single_line_if_else_max_width.2);
self.single_line_let_else_max_width.2 =
adjust(self.single_line_let_else_max_width.2);
self.struct_lit_width.2 = adjust(self.struct_lit_width.2);
self.struct_variant_width.2 = adjust(self.struct_variant_width.2);

self.max_width.2 = adjust(self.max_width.2);
self.set_heuristics();
}

fn set_width_heuristics(&mut self, heuristics: WidthHeuristics) {
let max_width = self.max_width.2;
let get_width_value = |
Expand Down
6 changes: 2 additions & 4 deletions src/macros.rs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Question:

  • The edge case where this would affect formatting would be if e.g. max_width was set to 80, and fn_call_width was set to 76, and the macro body indent was 8. In today's world, fn_call_width in the macro body would be larger than the max_width within the macro body (72), and would get clamped down to 72. With this fix, both would be reduced by 8 (max_width 80 -> 72, fn_call_width 76 -> 68).

Is there a (albeit synthetic) test that can demonstrate the after effect (of this edge case)?

Copy link
Copy Markdown
Author

@csmulhern csmulhern May 29, 2026

Choose a reason for hiding this comment

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

I've added a prior commit in the chain that adds test fixtures for these macro use cases, with both default heuristics, and manually crafted widths that will trigger this edge case when the macro body indent exceeds the difference between the custom width and max_width.

Note that the error log currently happens whenever the "clipping" behavior occurred, which is for any file with a macro body if you have custom width values close to / equal to max_width.

This change fixes the error logging, but it only effects formatting when the length of a line is specifically within the gap between max_width - <custom width> and max_width - <macro body indent>.

The long variant of these newly added tests is the macro arm specifically crafted to trigger these conditions. You'll notice that the long variant is the same in source and target before the commit.

When it comes to this being formatting affecting:

  1. I believe these edge cases should be formatted the way they are post-commit. The user expressed for e.g. fn_call_width to be less than max_width, and so clipping to max_width feels inappropriate.
  2. format_macro_bodies is an unstable option, so I believe formatting changes should be tolerated here regardless of compatibility.

Copy link
Copy Markdown
Member

@jieyouxu jieyouxu May 30, 2026

Choose a reason for hiding this comment

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

Thanks for the detailed explanation, this is what I was having trouble figuring out 👍

Note

I consider this review chain resolved, but intentionally leaving open for other reviewers for context.

Comment thread
jieyouxu marked this conversation as resolved.
Comment thread
jieyouxu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1339,15 +1339,13 @@ impl MacroBranch {
} else {
shape.indent.block_indent(&config)
};
let new_width = config.max_width() - body_indent.width();
config.set().max_width(new_width);
config.reduce_max_width(body_indent.width());

// First try to format as items, then as statements.
let new_body_snippet = match crate::format_snippet(&body_str, &config, true) {
Some(new_body) => new_body,
None => {
let new_width = new_width + config.tab_spaces();
config.set().max_width(new_width);
config.increase_max_width(config.tab_spaces());
match crate::format_code_block(&body_str, &config, true) {
Some(new_body) => new_body,
None => {
Expand Down
21 changes: 21 additions & 0 deletions tests/source/issue-6651/custom_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// rustfmt-format_macro_bodies: true
// rustfmt-max_width: 82
// rustfmt-fn_call_width: 76

macro_rules! short {
() => {
m!(a, b, c);
};
}

macro_rules! medium {
() => {
m!(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, ddddddddd);
};
}

macro_rules! long {
() => {
m!(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, ddddddddddddddd);
};
}
19 changes: 19 additions & 0 deletions tests/source/issue-6651/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// rustfmt-format_macro_bodies: true

macro_rules! short {
() => {
m!(a, b, c);
};
}

macro_rules! medium {
() => {
m!(aaaaaaaaaa, bbbbbbbbbb, cccccccccc, ddddddddd);
};
}

macro_rules! long {
() => {
m!(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, ddddddddddddddd);
};
}
26 changes: 26 additions & 0 deletions tests/target/issue-6651/custom_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// rustfmt-format_macro_bodies: true
// rustfmt-max_width: 82
// rustfmt-fn_call_width: 76

macro_rules! short {
() => {
m!(a, b, c);
};
}

macro_rules! medium {
() => {
m!(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, ddddddddd);
};
}

macro_rules! long {
() => {
m!(
aaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccc,
ddddddddddddddd
);
};
}
24 changes: 24 additions & 0 deletions tests/target/issue-6651/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// rustfmt-format_macro_bodies: true

macro_rules! short {
() => {
m!(a, b, c);
};
}

macro_rules! medium {
() => {
m!(aaaaaaaaaa, bbbbbbbbbb, cccccccccc, ddddddddd);
};
}

macro_rules! long {
() => {
m!(
aaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccc,
ddddddddddddddd
);
};
}
Loading