Skip to content

Cannot set granular width configuration settings to 100% if max_width = 100  #5404

Open

Description

@tcharding

Thanks for your work on rustfmt!

The granular width configuration settings are documented to be percentages of max_width (e.g. chain_width). However when max_width is set to 100 setting their values to 100 causes a warning

`fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`

The error message is both surprising and incorrect since fn_call_width = 100 is not a value that exceeds max_width.

This does not happen if max_width is 99 or 101.

The exact config options that trigger this bug are:

max_width = 100
use_small_heuristics = "Off"
fn_call_width = 100
$ rustfmt --version
rustfmt 1.4.38-stable (fe5b13d 2022-05-18)

Edit by @ytmimi (add a minimal reproducible code snippet):
macro_rules! foo {
    () => {};
}

Activity

  1. ytmimi commented on Jun 28, 2022

    @ytmimi
    Contributor

    Thanks for the report!

    Just as a heads up, when filing an issue please try to include a minimum code example that can be used to reproduce the issue along with any configuration options needed to produce the issue. It was a little tricky to track down since I tried running rustfmt on a few files and I couldn't reproduce the error until I ran rustfmt against src/utils.rs. Doing so the error is emitted 4 times.

    Running rustfmt with debug logging enabled (RUSTFMT_LOG=rustfmt=DEBUG rustfmt --config-path=issue_5404.toml --emit stdout src/utils.rs) reveals that this error is always emitted after calling replace_names:

    [2022-06-28T17:05:55Z DEBUG rustfmt_nightly::macros] replace_names `zsp.source_callsite()` {"$sp": "zsp"}
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    
    [2022-06-28T17:05:55Z DEBUG rustfmt_nightly::macros] replace_names `!zself.config.file_lines().is_all()
                    && !zself
                        .config
                        .file_lines()
                        .intersects(&zself.parse_sess.lookup_line_range(zspan))` {"$span": "zspan", "$self": "zself"}
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    
    [2022-06-28T17:05:55Z DEBUG rustfmt_nightly::macros] replace_names `if out_of_file_lines_range!(zself, zspan) {
                    return None;
                }` {"$self": "zself", "$span": "zspan"}
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    
    [2022-06-28T17:05:55Z DEBUG rustfmt_nightly::macros] replace_names `if out_of_file_lines_range!(zself, zspan) {
                    zself.push_rewrite(zspan, None);
                    return;
                }` {"$span": "zspan", "$self": "zself"}
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    

    replace_names only gets called in MacroBranch::rewrite

    rustfmt/src/macros.rs

    Lines 483 to 486 in c4416f2

    // Replaces `$foo` with `zfoo`. We must check for name overlap to ensure we
    // aren't causing problems.
    // This should also work for escaped `$` variables, where we leave earlier `$`s.
    fn replace_names(input: &str) -> Option<(String, HashMap<String, String>)> {

    And on line 1230 we adjust the max_width, which is where the issue is occurring.

    rustfmt/src/macros.rs

    Lines 1216 to 1230 in c4416f2

    let (body_str, substs) = replace_names(old_body)?;
    let has_block_body = old_body.starts_with('{');
    let mut config = context.config.clone();
    config.set().hide_parse_errors(true);
    result += " {";
    let body_indent = if has_block_body {
    shape.indent
    } else {
    shape.indent.block_indent(&config)
    };
    let new_width = config.max_width() - body_indent.width();
    config.set().max_width(new_width);

    Config::set returns a ConfigSetter, and trying to set any of the width settings results in calling Config::set_heuristics.

    impl<'a> ConfigSetter<'a> {
    $(
    #[allow(unreachable_pub)]
    pub fn $i(&mut self, value: $ty) {
    (self.0).$i.2 = value;
    match stringify!($i) {
    "max_width"
    | "use_small_heuristics"
    | "fn_call_width"
    | "single_line_if_else_max_width"
    | "attr_fn_like_width"
    | "struct_lit_width"
    | "struct_variant_width"
    | "array_width"
    | "chain_width" => self.0.set_heuristics(),

    Config::set_heuristics calls Config::set_width_heuristics.

    fn set_heuristics(&mut self) {
    let max_width = self.max_width.2;
    match self.use_small_heuristics.2 {
    Heuristics::Default =>
    self.set_width_heuristics(WidthHeuristics::scaled(max_width)),
    Heuristics::Max => self.set_width_heuristics(WidthHeuristics::set(max_width)),
    Heuristics::Off => self.set_width_heuristics(WidthHeuristics::null()),
    };
    }

    Config::set_width_heuristics pretty much goes through all the width settings one by one and calls the get_width_value closure on each, leading to the error that you're seeing:

    fn set_width_heuristics(&mut self, heuristics: WidthHeuristics) {
    let max_width = self.max_width.2;
    let get_width_value = |
    was_set: bool,
    override_value: usize,
    heuristic_value: usize,
    config_key: &str,
    | -> usize {
    if !was_set {
    return heuristic_value;
    }
    if override_value > max_width {
    eprintln!(
    "`{0}` cannot have a value that exceeds `max_width`. \
    `{0}` will be set to the same value as `max_width`",
    config_key,
    );
    return max_width;
    }
    override_value
    };

  2. added
    only-with-optionRequires a non-default option value to reproduce
    A-macrosArea: macros (procedural macros, macro_rules! macros, etc.)
    on Jun 28, 2022
  3. calebcartwright commented on Jun 28, 2022

    @calebcartwright
    Member

    fwiw I know this has been reported before, but can't find the prior issues atm

  4. tcharding commented on Jun 28, 2022

    @tcharding
    Author

    Wow @ytmimi, thorough research/response, thank you. I'll add the minimal repro next time.

  5. iago-lito commented on Apr 25, 2023

    @iago-lito

    Hi! I can also reproduce with the following:

    .rustfmt.toml

    max_width = 100 # Not strictly necessary to reproduce.
    single_line_if_else_max_width = 100 # Any value above 92 produces the incorrect warning.

    src/main.rs

    macro_rules! anything { // Necessary to reproduce.
        () => {};
    }
    
    fn main() {
        let v = if a == b { String::new() } else { format!("anything") }; // Formatting does happen.
    }

    The following incorrect warning is then displayed:

    $ cargo fmt
    `single_line_if_else_max_width` cannot have a value that exceeds `max_width`. `single_line_if_else_max_width` will be set to the same value as `max_width`

    Although the project seems correctly formatted. Was this minimal reproductible example the only blocking point?

  6. ferdonline commented on Oct 24, 2023

    @ferdonline
  7. ytmimi commented on Oct 24, 2023

    @ytmimi
  8. ferdonline commented on Oct 26, 2023

    @ferdonline
  9. ytmimi commented on Oct 26, 2023

    @ytmimi
  10. bergkvist commented on Feb 16, 2024

    @bergkvist

    I just experienced the same problem and put a few print statements into the rustfmt source code, which revealed the following behaviour:

    // max_width = 100
    // fn_call_width = 100
    
    macro_rules! foo {
        () => {
            // max_width = 92
            // fn_call_width = 100
    
            // WARNING: `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
            // Which results in:
            // fn_call_width = 92
        };
    }

    Notice that rustfmt decreases max_width as it enters deeper scopes/nesting, but fn_call_width remains the same. I guess this saturation is actually desirable behaviour, but getting the context-less warning message causes more confusion than anything.

    Interestingly, if you add an extra pair of brackets, max_width in the nested scope will be 96 instead of 92:

    // max_width = 100
    // fn_call_width = 100
    
    macro_rules! foo {
        () => {{
            // max_width = 96
            // fn_call_width = 100
    
            // WARNING: `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
            // Which results in:
            // fn_call_width = 96
        }};
    }
  11. csmulhern commented on Sep 16, 2025

    @csmulhern
    Contributor

    I have addressed this in #6651.

  12. added
    SO-max_widthStable option: max_width
    and removed on Feb 20, 2026
  13. jieyouxu commented on Nov 2, 2026

    @jieyouxu
    Member

    Reopening to track revert #7222 (#7223); we'll probably have to reland #6651 with a style edition gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-macrosArea: macros (procedural macros, macro_rules! macros, etc.)P-lowLow prioritySO-fn_call_widthStable option: fn_call_widthSO-max_widthStable option: max_widthonly-with-optionRequires a non-default option value to reproduce

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions