Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of attribute in enum #6286

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

malikolivier
Copy link

@malikolivier malikolivier commented Aug 19, 2024

Fix #5662.

Previously, an enum item containing an attribute (rustdoc or macro for example) would be considered multi-line, thus forcing the formatting strategy of all the items in the enum to be Vertical (i.e. multi-line formatting).

This PR does the following:

  • Fixes the issue (ce990c8) and gate the changes (103d13e, 5601462)
  • Handle additional edge-cases involving attributes (277cb90, 3d1b831)
    • There may be a better (shorter?, by using an appropriate lib) for an implementation, but could not figure it out as now. I believe this implementation works. If the maintainer believes code should be moved, re-architected in any way, please feel free to give feedback.
  • Add exhaustive tests (6f204cd, df5b6d9, 933e997)
  • Fix rustfmt's formatting after the issue is fixed (757e73d, 22b076c)

TODO

This fix was made thanks to the hint at [1].
This was reported in issue rust-lang#5662 [2].

Previously, a enum item containing an attribute (rustdoc or macro) would
be considered multi-line, thus forcing the formatting strategy of all
the items in the enum to be Vertical (i.e. multi-line formatting).

When determining the formatting strategy for enum items, we should
ignore the attributes. This is what we do in the `is_multi_line_variant`
function. Or else, simply adding a rustdoc comment or a macro attribute
would cause the formatting of the whole enum to change, which is not a
desirable behavior.

We will be adding tests in the following commits.

- [1] rust-lang#5662 (comment)
- [2] rust-lang#5662
Test case as reported in rust-lang#6280.
This test case used to fail, but the code in the previous commit fixes
it.

We followed instructions on Contributing.md [1] to create a test case.

`tests/target/rust-doc-in-enum/vertical-no-doc.rs` is being left unformatted
(expected behavior), while `tests/target/rust-doc-in-enum/vertical-with-doc.rs`
is formatted to `C { a: usize }` (unexpected behavior).

The only different between the two samples is the `/// C` rustdoc added
in `with-doc.rs`.
This reproducing example is minimal: for example, after removing `d: usize`
we do not reproduce the reported behavior.

We found this issue while adding rustdocs to an existing project.
We would expect that adding a line of documentation should not cause the
formatting of the code to change.

[1] https://github.com/rust-lang/rustfmt/blob/40f507526993651ad3b92eda89d5b1cebd0ed374/Contributing.md#L33
The enums modified here were not properly formatted.
The fix in the previous commit now formats them properly.

-> Regardless of the presence of comments or macro attributes, if any of
the enum items is multi-line, all enum items should be formatted as
multi-line.
…stivity

Current naive implementation fails with multi-line macro attributes.
@ytmimi
Copy link
Contributor

ytmimi commented Aug 19, 2024

https://www.github.com/rust-lang/rustfmt/issues/5662#issuecomment-1386231624 mentions that a new config option would be needed to allow both single line and multi-line enum variants. I am not sure to understand why. Given some guidance from the maintainer, I can certainly add a new config option in this PR.

It's been a while since I made that comment but if I'm remembering correctly the idea with the new configuration was to allow mult-line and single-line variants to co-exist in an enum definition. Currently we'll try to rewrite all variants as multi-line if any of them are multi-line. I don't think it's necessary to add that option, but hopefully that gives you some additional context.

src/items.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@ytmimi ytmimi left a comment

Choose a reason for hiding this comment

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

@malikolivier I appreciate you looking into this one. I know you mentioned that multi-line attributes are a problem for the current approach. I'd prefer if we tried to resolve that issue before moving forward.

@malikolivier
Copy link
Author

malikolivier commented Aug 19, 2024

Thank you for the review!

https://www.github.com/rust-lang/rustfmt/issues/5662#issuecomment-1386231624 mentions that a new config option would be needed to allow both single line and multi-line enum variants. I am not sure to understand why. Given some guidance from the maintainer, I can certainly add a new config option in this PR.

It's been a while since I made that comment but if I'm remembering correctly the idea with the new configuration was to allow mult-line and single-line variants to co-exist in an enum definition. Currently we'll try to rewrite all variants as multi-line if any of them are multi-line. I don't think it's necessary to add that option, but hopefully that gives you some additional context.

Thank you! I too believe the current approach of not having multi-line and single-line variants co-exist in an enum definition is all right. So I won't add any option.

I know you mentioned that multi-line attributes are a problem for the current approach. I'd prefer if we tried to resolve that issue before moving forward.

Got it! Will attempt to have a complete fix with no edge-case left ;)
Depending on the amount of changes, I may close this PR and re-open a new one.

…t fix"

This reverts some part of commit 757e73d.

The `enum.rs` should still be formatted with the default style edition.
So no change should happen for those files.
src/items.rs Outdated Show resolved Hide resolved
The project has been using `>= StyleEdition::Edition2024` to indicate new formatting,
and it's easier to grep for `Edition2021` to find all of the older formatting points. [1]

[1] rust-lang#6286 (comment)
We properly handle multi-line attributes in front of an enum variant.

There are several types of attributes [1], but the only attributes that
can occur in this context are outer attributes like
`#[repr(transparent)]`, `/// Example` or `/** Example */`.

This commit deals with macro attributes like `#[repr(transparent)]`.
We implement our own trivial macro attribute parser to exclude them from
the variant definition, as we could not find a easy way of re-using
existing parsing code (with the `syn` crate or `rustc_parse`).

We will deal with outer documentation blocks in the next commit.

[1] https://docs.rs/syn/2.0.75/syn/struct.Attribute.html
We properly handle outer documentation blocks in front of an enum
variant.

With this commit, we believe we have handled all edge-cases regarding
attributes in front of enum variants.
@malikolivier
Copy link
Author

malikolivier commented Aug 21, 2024

I'd prefer if we tried to resolve that issue before moving forward.

@ytmimi I pushed code to the PR that resolves the issue (multi-line macro attributes: 277cb90, documentation block: 3d1b831). Can you review again when you are available?

(at your request, I can rebase. This time, for easier review, I've added a few commits to fix the mentioned issues)

Instead of returning `Option<String>`, `format_variant` now returns
`(Option<String>, bool).

Maybe this could be `Option<(String, bool)>` instead though?
Copy link
Contributor

@ytmimi ytmimi left a comment

Choose a reason for hiding this comment

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

Thanks for your patience on the follow up review. I've pushed one commit that I think will simplify things here. Let me know if you have any questions.

src/items.rs Outdated
Comment on lines 698 to 744
// Skip macro attributes in variant_str
// We skip one macro attribute per loop iteration
loop {
let mut macro_attribute_found = false;
let mut macro_attribute_start_i = 0;
let mut bracket_count = 0;
let mut chars = variant_str.chars().enumerate();
while let Some((i, c)) = chars.next() {
match c {
'#' => {
if let Some((_, '[')) = chars.next() {
macro_attribute_start_i = i;
bracket_count += 1;
}
}
'[' => bracket_count += 1,
']' => {
bracket_count -= 1;
if bracket_count == 0 {
// Macro attribute was found and ends at the i-th position
// We remove it from variant_str
let mut s =
variant_str[..macro_attribute_start_i].trim().to_owned();
s.push_str(variant_str[(i + 1)..].trim());
variant_str = s;
macro_attribute_found = true;
break;
}
}
'\'' => {
// Handle char in attribute
chars.next();
chars.next();
}
'"' => {
// Handle quoted strings within attribute
while let Some((_, c)) = chars.next() {
if c == '\\' {
chars.next(); // Skip escaped character
} else if c == '"' {
break; // end of string
}
}
}
_ => {}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This seemed pretty complicated and it was hard to tell how accurate this was. I thought it might be simpler if we just got the multi-lined property when rewriting the variant body so we wouldn't need to parse it out after the fact. I've pushed one commit as a proof of concept / reference for you to take a look at. Hopefully it'll serve as a good jumping off point for you.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you. Will look at it when I have time (probably next week).

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

Successfully merging this pull request may close these issues.

Avoiding extra linebreaks when doc-comment added to enum
3 participants