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/Inconsistent Struct Body Opening Brace Placement After Where Clause #5508

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

Conversation

jmj0502
Copy link
Contributor

@jmj0502 jmj0502 commented Aug 18, 2022

Fixes #5507

  • A new function was added to items.rs. set_brace_pos will format generic structs with where clauses appropriately based on the version provided as a parameter.
  • Different tests case for both indent_styles were added.

@jmj0502 jmj0502 marked this pull request as ready for review August 22, 2022 19:21
@jmj0502
Copy link
Contributor Author

jmj0502 commented Aug 22, 2022

@ytmimi Done! Thanks for the version gate clue btw c:
Let me know if there's something else we need to add!

src/items.rs Outdated
Comment on lines 1265 to 1286
fn set_brace_pos(has_fields: bool, has_content: bool, version: Version) -> BracePos {
return match version {
Version::One => {
if has_fields {
return BracePos::Auto;
}
BracePos::ForceSameLine
}
Version::Two => {
match (has_fields, has_content) {
// Doesn't have fields, there is nothing between {}, has generics.
(true, true) => BracePos::ForceSameLine,

// Doesn't have fields, has something between { }, has generics.
(true, false) => BracePos::Auto,

// Has fields, has generics.
(false, _) => BracePos::Auto,
}
}
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Couple of notes:

  1. the return in return match version is redundant, since the match is the only statement in this function. remove the trailing semicolon from the match statement to convert it to an expression and have it implicitly return its value.
  2. I'm all for extracting this logic into a stand alone function, but I think we can simplify the logic see my next comment for details.

src/items.rs Outdated
Comment on lines 1292 to 1317
if fields.is_empty() {
BracePos::ForceSameLine
} else {
BracePos::Auto
},
set_brace_pos(
!fields.is_empty(),
span.hi() <= body_lo + BytePos(5),
Copy link
Contributor

Choose a reason for hiding this comment

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

To be honest, this check span.hi() <= body_lo + BytePos(5) has got me scratching my head and it's not obvious from reading the code what it's supposed to be doing. I'm all for moving this logic into a standalone function, but I think this check could be simplified to something like:

            if fields.is_empty() {
                let snippet = context.snippet(mk_sp(body_lo, span.hi()));
                let body_contains_commets = contains_comment(snippet);
                if context.config.version() == Version::Two && body_contains_commets {
                    BracePos::Auto
                } else {
                    BracePos::ForceSameLine
                }
            } else {
                BracePos::Auto
            }

Comment on lines 12 to 15
struct BlockComment<T>
where T: Eq {
/* block comment */
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also add a MultiLineBlockComment case

struct MultiLineBlockComment<T>
    where T: Eq {
    /* block comment 
     * block comment
     */
}

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 work on this.

As noted in the original issue we didn't need any configuration options to produce the inconsistent opening brace placement. For that reason we don't need indent_style=Visual and indent_style=Block tests. However, it would be nice to have an explicit version=One and version=Two test.

Also, I feel like it's fine to place the braces on the same line in the empty EmptyBody and single-line BlockComment cases. I'd expect in the MultiLineBlockComment case that we'd put the opening brace on its own line.

@jmj0502
Copy link
Contributor Author

jmj0502 commented Aug 23, 2022

@ytmimi Cool! I'll start to with the changes now! The contains_comments function will be super helpful (it will allow me to simplify a lot of code) ✌️

…y (based on the comments received on the review process). The test cases were improved, in order to emphasize the fix.
@jmj0502
Copy link
Contributor Author

jmj0502 commented Aug 23, 2022

@ytmimi The changes are done! Thanks for all the comments and guidance (they'll sure help me improve as a rustacean 🦀), I really appreciate it!

The whole experience contributing to rustfmt has been awesome and I'm really looking forward to keep on making contributions! c:

Comment on lines 3 to 6
struct EmptyBody<T>
where
T: Eq,
{}
Copy link
Contributor

Choose a reason for hiding this comment

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

Subjective, but I think this should stay on the same line as the bound, Essentially following the version=One formatting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okok. I'll proceed to fix it

Comment on lines 25 to 28
struct BlockComment<T>
where
T: Eq,
{/* block comment */}
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, I think single line block comments should also follow the version=One formatting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a way to specifically check for block comments? If so, I the solution shouldn't be too complicated.

Copy link
Contributor

Choose a reason for hiding this comment

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

To my knowledge there isn't a standalone function that can be used for something like that. However you could check that using CommentStyle, which has a is_block_comment method. You'd use the comment_style function to construct one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ytmimi Done! Now empty impl blocks and impl blocks that only contain single line block comments have the same format on each version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fantastic. Thank you for making those changes. I'll give this another review in the coming days!

… line block comments are formatted equally in V2 and V1.
… in order to avoid unnecesary duplication. [items.rs - 1286].
@jmj0502
Copy link
Contributor Author

jmj0502 commented Oct 8, 2022

@ytmimi Hey! Hope you're doing great!

I made a little change, in order to remove some redundant code. I was checking for fields.is_empty() and body_has_comments in the match expression located on items.rs - 1302, despite the fact that such match expression is contained on an if block that checks for fields.is_empty(). Now I just check for body_has_comments.

It was just a little change, but I thought that you should know about it. Have an awesome weekend!

src/items.rs Outdated
Comment on lines 1265 to 1312
fn set_brace_pos(
context: &RewriteContext<'_>,
struct_parts: &StructParts<'_>,
fields: &[ast::FieldDef],
body_lo: BytePos,
) -> BracePos {
if fields.is_empty() {
let span = struct_parts.span;
let generics_lo = if let Some(generics) = struct_parts.generics {
generics.span.lo()
} else {
body_lo
};
let snippet = context.snippet(mk_sp(generics_lo, span.hi()));
let body_contains_comments = contains_comment(snippet);
let has_single_line_block_comment = if body_contains_comments {
let comment_start = match snippet.find('/') {
Some(i) => i,
None => 0,
};
let comment_snippet = &snippet[comment_start..];
let comment = comment_style(comment_snippet, false);
let is_block_comment = comment.is_block_comment();
let is_single_line_comment = if is_block_comment {
let comment_end = match find_comment_end(comment_snippet) {
Some(i) => i,
None => comment_start,
};
is_single_line(&comment_snippet[..=comment_end - 1])
} else {
false
};
is_single_line_comment
} else {
false
};

if context.config.version() == Version::Two {
return match body_contains_comments {
true if has_single_line_block_comment => BracePos::ForceSameLine,
true => BracePos::Auto,
false => BracePos::ForceSameLine,
};
}
return BracePos::ForceSameLine;
}
BracePos::Auto
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I took a look, and I think we could simplify the logic here as follows:

Note: I renamed set_brace_pos to set_struct_brace_pos to be more explicit.

fn set_struct_brace_pos(
    context: &RewriteContext<'_>,
    fields: &[ast::FieldDef],
    span: Span,
) -> BracePos {
fn set_struct_brace_pos(
    context: &RewriteContext<'_>,
    fields: &[ast::FieldDef],
    span: Span,
) -> BracePos {
    if !fields.is_empty() {
        return BracePos::Auto;
    }

    // At this point we know there aren't any struct fields
    // This snippet contains the unformatted, trimmed struct body
    let snippet = context.snippet(span).trim();

    if snippet.is_empty() || context.config.version() == Version::One {
        return BracePos::ForceSameLine;
    }

    // The body isn't empty so there must be comments.
    // we also know that we're implicitly using Version Two formatting.
    let is_single_line = is_single_line(snippet);
    let is_block_comment = comment_style(snippet, false).is_block_comment();

    if is_single_line && is_block_comment {
        BracePos::ForceSameLine
    } else {
        BracePos::Auto
    }
}

At the call site we can hoist inner_span from line 1329 so we can use that as the span argument to set_struct_brace_pos. Let me know if all that makes sense.

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.

@jmj0502 apologies for the delay on the follow up review. I've looked this over and I like the approach of encapsulating the brace position logic into a separate function. I've left some thoughts on how we could simplify it. Let me know your thoughts and if you think I've missed anything.

@jmj0502
Copy link
Contributor Author

jmj0502 commented Oct 12, 2022

@ytmimi Hey! Hope you're doing well!

No worries, I know that you have other occupations, so I'm ok with PRs not being reviewed instantly 👍.

The approach you decided to take seems super cool, in fact, I like the way most of the logic could be stripped down to some simple steps based on the changes made to the function signature.

I'll implement the changes and let you know when I'm done! c:

src/items.rs Outdated
Comment on lines 1341 to 1316
set_brace_pos(&context, &struct_parts, &fields, body_lo),
set_struct_brace_pos(&context, &fields, mk_sp(body_lo, span.hi() - BytePos(1))),
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of creating the inner_span inline with mk_sp(body_lo, span.hi() - BytePos(1)) can we just hoist it up from where it's originally defined.

+ let inner_span = mk_sp(body_lo, span.hi() - BytePos(1));
  let generics_str = match struct_parts.generics {
         Some(g) => format_generics(
             context,
             g,
             context.config.brace_style(),
-            set_brace_pos(&context, &struct_parts, &fields, body_lo),
+            set_struct_brace_pos(&context, &fields, inner_span)),
             ..
         )
         ..
 };
 ...
     if fields.is_empty() {
-        let inner_span = mk_sp(body_lo, span.hi() - BytePos(1));
         format_empty_struct_or_tuple(context, inner_span, offset, &mut result, "", "}");
         return Some(result);
     }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ytmimi Hey! Hope you're doing awesome!

I was about to ask you a question about this point hehe (hositing the inner_span on the outermost scope of the function). I'll add the changes in a few minutes 👍.

…st scope of the format_struct_struct function.
@jmj0502
Copy link
Contributor Author

jmj0502 commented Oct 14, 2022

@ytmimi Donee! thanks again for all the guidance and the help. This fix really allowed me to understand some parts of Rustfmt a little bit better (in regard to the use of some utility functions, structs, etc). I'm really glad that I'm getting to know the project better and I'm definitely looking forward to keeping on making contributions.

I really appreciate all the things you've taught me; they'll sure help me improve the quality of feature contributions. I'll be ready in case any change is needed after the review c:

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.

@jmj0502 thank you for the kind words and for all the effort you're putting in to help solve these issues. The team really appreciates it! I also love to hear that you're feeling more comfortable with the codebase!

I think the PR is good to go, and I'm happy to move forward. We're still waiting on a few PRs to land in the rust-lang/rust repo, and we'll likely merge this after we sync those changes into rustfmt.

@jmj0502
Copy link
Contributor Author

jmj0502 commented Jan 22, 2023

@ytmimi Hey! Hope you're doing awesome! Btw, happy new year! 🥳

Sorry that I've been off for a while. I wasn't very active over the course of the last two months due to different reasons including a back injury and some other personal stuff. How you've been?

Is it possible to merge this PR? #5460 depends on this one and it's technically done as well. I'll be looking forward to integrate this changes into #5460 once they land, in order to proceed and upload the updated version of the branch.

Looking forward to hearing from you!
Have a nice day!

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.

Inconsistent Struct Body Opening Brace Placement After Where Clause
2 participants