rustfmt
rustfmt copied to clipboard
Errantly formatting multiline macro
I have this macro call here that is formatted by rustfmt as so.
$ rustfmt --check godot-macros/src/class/data_models/field_export.rs
Diff in /home/fp/3rd/gdext/godot-macros/src/class/data_models/field_export.rs at line 402:
}?;
let deprecation_warning = if *radians {
quote! {
- #export_func;
- ::godot::__deprecated::emit_deprecated_warning!(export_range_radians);
- }
+ #export_func;
+ ::godot::__deprecated::emit_deprecated_warning!(export_range_radians);
+ }
} else {
quote! { #export_func }
};
$ rustfmt --version
rustfmt 1.7.0-stable (129f3b9 2024-06-10)
You can see here that rustfmt wants to un-indent the macro contents and double indent the end bracket for some reason.
Here's aa MCVE for this issue. The important details are that the innermost expressions are within a curly bracket-delimited macro, which itself is within 3 levels of scope.
// correct formatting
fn bar() {
{
{
foo! {
export_func;
warning;
}
}
}
}
// rustfmt output
fn bar() {
{
{
foo! {
export_func;
warning;
}
}
}
}
Hey I'm continuing to get errors related to this. I'd be happy to do some work to come up with better reproduction steps, but I'd need help from a maintainer to figure out how.
@fpdotmonkey I'm also unable to reproduce the issue that you described. Are you using any other configuration options? Also, what version of rustc are you using?
Here's some version and config information
$ rustfmt --version
rustfmt 1.7.0-stable (3f5fd8d 2024-08-06)
$ rustc --version
rustc 1.80.1 (3f5fd8dd4 2024-08-06)
$ cat ~/.rustfmt.toml
edition = "2021"
$ cat ~/rustfmt.toml
cat: /home/fp/rustfmt.toml: No such file or directory
$ ls ~/.config/rustfmt
ls: cannot access '/home/fp/.config/rustfmt': No such file or directory
So rustc and rustfmt are installed the regular way through rustup, and the only config I have is a more recent edition. I did previously have edition = "2024" instead of 2021 in the config, but the issue occurs regardless of that and also if I specify the edition in the cli, --edition 2021.
It's also worth noting that the CI for the project I've referenced would fail if it caught a diff from rustfmt, so it's at least reproducing that the above formatting isn't corrected to the intuitively correct one.
@fpdotmonkey if you change the formatting to what you expect it to be does CI pass or does CI fail when it checks formatting?
It's hard to say with the particular problem that's referenced at the top of this issue; that was merged months ago. I encountered something that's possibly related in https://github.com/godot-rust/gdext/pull/876#issuecomment-2323063264, and that does fail on account of rustfmt https://github.com/godot-rust/gdext/actions/runs/10655828310/job/29533814938?pr=876
If you're able to put together a minimal reproduction for the new snippet that you've linked to that would be great
Sure thing. This is a bit different from what occurs in that comment. There, only the #[doc] macros are one tab too little indent, but here, it's the whole quote! body that's one level too little.
// manual formatting
fn main() {
let functions = quote! {
#[doc = "Documentation"]
#[inline]
fn foo() {}
};
}
// rustfmt output from the previous snippet
fn main() {
let functions = quote! {
#[doc = "Documentation"]
#[inline] // In the issue that I encountered, here and below was indented one tab further, which would be correct
fn foo() {}
};
}
I'm not sure why this is different from the linked example, but it's at least reproducible and similar behavior.
I did a little more probing at the wrong formatting from the top of this issue and was able to create a MCVE.
// correct formatting
fn bar() {
{
{
foo! {
export_func;
warning;
}
}
}
}
// rustfmt output
fn bar() {
{
{
foo! {
export_func;
warning;
}
}
}
}
Notes on this:
- It's required that there be a macro call---replacing it with scope, conditions, or similar won't cause the issue
- It's required that the macro have curly bracket invocation---parentheses and square brackets won't reproduce
- It's required that the macro is contained within 3 or more levels of scope
- The scope can come from conditions, functions, trait impls, or inherent impls (at least)
- There's also an odd behavior where if I replace
foo!withif foo, it will format correctly (expected), but then when I change it back fromif footofoo!, it won't reproduce, despite being the same code as the initial sample. If I clear the buffer and paste in the above example, it will reproduce again.
Hey is there anything more needed for a MCVE?
Hey is there anything more needed for a MCVE?
You're all set. The new snippet demonstrates the issue. You may want to go back and amend the description to include the MCVE.
I believe I have ran into a similar issue with proptest!: https://github.com/jaskij/rustfmt-proptest-confusion-repro/blob/main/src/lib.rs
Strangely enough, the indentation in that file is what rustfmt insists on, both the one shipped with 1.82 and nightly.
$ cargo fmt --version
rustfmt 1.7.1-stable (f6e511e 2024-10-15)
$ cargo +nightly fmt --version
rustfmt 1.8.0-nightly (33c245b9e9 2024-12-10)
@jaskij your issue doesn't seem to be related. Your proptest! macro invocation doesn't parse as valid rust code so rustfmt won't be able to format it at all. Specifically s in "\\PC*" isn't a valid function argument. Because rustfmt won't format the macro call you can format it by hand and rustfmt won't mess with it. For example:
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::Item;
proptest! {
#[test]
fn read_item_chars(s in "\\PC*") {
let pi = Item {first_long_name_a: 0,item_count: 0,area: 0,address: 0};
}
}
}
@ytmimi you say that, and yet rustfmt does mess with the indentation there. Take my repro and try changing just the indentation and formatting it. rustmft does restore the indentation to how it is in git. Since it's touching the code at all, I'd expect it to do something reasonable. But with your explanation, this seems like a separate issue.
Edit:
~~My bad. Turns out that, despite me having set it to always use rustfmt, RustRover does some of it's own formatting on top of that in this case.~~ Did some more testing, no, it wasn't RustRover. But it does seem somewhat inconsistent.
@jaskij please open a dedicated issues to describe your problem.