rust-lang / rustfmt

Format Rust code
https://rust-lang.github.io/rustfmt/
Apache License 2.0
6.01k stars 883 forks source link

Nested `macro_rules` utilising inner metavariable are infinitely indented with repeated formatting #4609

Open the6p4c opened 3 years ago

the6p4c commented 3 years ago

Input

macro_rules! outer {
    ($d:tt) => {
        macro_rules! inner {
            ($d s:expr) => {
                println!("{}", $d s);
            }
        }
    };
}

outer!($);

fn main() {
    inner!("hi");
}

Output Note that the body and trailing closing braces of the nested macro_rules block is indented. With further invocations of rustfmt, this block is continuously indented to the next level.

Removing the usage of the inner macro_rules's $s in the println! usage causes the formatting to behave as expected.

macro_rules! outer {
    ($d:tt) => {
        macro_rules! inner {
                    ($d s:expr) => {
                        println!("{}", $d s);
                    }
                }
    };
}

outer!($);

fn main() {
    inner!("hi");
}

Expected output The input should likely remain unchanged.

Meta

davidBar-On commented 3 years ago

The issue is caused because original code snippet is used, since the macro code cannot be parsed properly: a , is missing after the $d in println!("{}", $d s). Submitted PR #4629 with a proposed solution to the issue.

the6p4c commented 3 years ago

The original code does compile and execute as expected - I’m not sure what you mean by “cannot be parsed properly”.

davidBar-On commented 3 years ago

Oops! You are right. It is a while since I evaluated the root case .... Sorry for that. The real issue is that rusfmt doesn't know to distinguish between a $ parameter and other parameters (see issue #8), so it expects that there will be a , after the $d. This is the reason for using the original code snippet.

Note that the proposed fix is handling the general issue of formatting macro body when original code snippet is used, and not the specific issue of handling the $ items (which seem to require significant refactoring of the code).

chris-morgan commented 3 years ago

Another example:

macro_rules! alpha {
    () => {
        macro_rules! beta {
            () => {
                gamma!(*)
            };
        }
    };
}

Where I’ve written *, you look to be able to put anything that doesn’t parse as an expression.

I found a hint at a likely underlying problem: if you take just the inner macro_rules!, you get a case where it just gives up and leaves indentation as it is for lines 2–5, so that this is a stable wonky indentation:

macro_rules! delta {
 () => {
  epsilon!(*)
   };
    }

Whereas if you remove that * or replace it with something that can be parsed as an expression, it formats to what you’d expect.

davidBar-On commented 2 years ago

Submitted PR #5473 with a proposed solution.

B1Z0N commented 1 year ago

In case someone wants a quick fix for that and they are still not aware of rustfmt:skip.

image