Skip to content

Control-flow comments formatting #4578

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

Conversation

davidBar-On
Copy link
Contributor

A proposed fix for issue #4549 for control-flow comments formatting. The approach taken is:

  1. "pre-else" comments continue to be in a separate line, to be compatible with the formatting of the "pre-if" comments.
  2. The "post-if/else" comments and "post-condition / pre-block" comments are formatted using combine_strs_with_missing_comments(), instead of just putting them in a separate line.

The general output with these changes is:

    /*pre-if*/
    if /*post-if*/ 5 /*x*/ == /*y*/ 6 /*pre-if-block*/ {
        x = y + 7;
    }
    /*pre-elseif*/
    else if /*post-elseif*/ z == u /*pre-elseif-block*/ {
        y = 5;
    }
    /*pre-else*/
    else /*post-else*/ {
        z = x;
    }

This change requires some changes to existing test cases, but it seems that at least in most of these cases this change is what is desired. For example, currently rustfmt formats the following from else-if-brace-style-closing-next-line.rs:

    if false // lone if comment

as:

    if false
    // lone if comment

With the change the output format is:

    if false // lone if comment 

which seem to be better, as the comment remains in the same line of the if as in the source.

@calebcartwright
Copy link
Member

With the change the output format is:

    if false // lone if comment 

which seem to be better, as the comment remains in the same line of the if as in the source.

Ideally rustfmt would honor the original comment placement when formatting so that the association and meaning is maintained, but we obviously fail to do that in many cases.

Are the changes you're proposing here supporting that, or would they also move next line comments up to the same line as the conditional?

E.g. how would these two end up being formatted (assuming items in the bodies)

    if false // i probably say something about the condition
    {
    }

    if false
    // maybe i wanted my comment here to describe the block and not the condition :)
    {
    }

@davidBar-On
Copy link
Contributor Author

Are the changes you're proposing here supporting that, or would they also move next line comments up to the same line as the conditional?

The change does preserve the same/next line placement of the comment, which is the result of using combine_strs_with_missing_comments(). The formatting output of the given code is:

    if false // i probably say something about the condition
    {}

    if false
    // maybe i wanted my comment here to describe the block and not the condition :)
    {}

@calebcartwright
Copy link
Member

Could you take a look at the corresponding Style Guide section and see if the proposed changes result in formatting that aligns?

My sense is that regardless of the root causes that requires breaking the control line (be it a comment, width, nature of the expression, etc.) then the line breaking indentation rules should still be applied.

Note that we may already be failing to align with the prescribed formatting in the current state with some of these cases with comments, and if we're going to update the comment handling then we may as well go ahead and ensure full alignment with the style guide.

Also, let's be sure to include a wide variety of tests here, since this formatting applies to all control flow expressions (not just If blocks)

@davidBar-On
Copy link
Contributor Author

Also, let's be sure to include a wide variety of tests here, since this formatting applies to all control flow expressions (not just If blocks)

I have added test cases for all the control flow expressions, including a fix for for which I found while adding these test cases.

Could you take a look at the corresponding Style Guide section and see if the proposed changes result in formatting that aligns? ... Note that we may already be failing to align with the prescribed formatting in the current state with some of these cases with comments, and if we're going to update the comment handling then we may as well go ahead and ensure full alignment with the style guide.

The guidelines doesn't say much about comments formatting and I didn't find any issue with their formatting.

The main issue in the current formatting vs. the guideline seem to be related to "... prefer to break before the = in * let expressions and before in in a for expression". There are several examples in the the test cases that do not comply with these guidelines. For example, current formatting of the following code is:

    if let Some(x) =
        (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)

while according to the guidelines it should be formatted as:

    if let Some(x)
        = (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)

Copy link
Member

@calebcartwright calebcartwright left a comment

Choose a reason for hiding this comment

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

Thank you for the PR. Have only done a cursory glance through but left a few inline feedback items.

To be fully transparent, I'm not sure when I may have a chance to fully review this one and consider merging because my bandwidth is somewhat limited and there's more pressing/higher priority rustfmt items that need attention

Comment on lines -900 to -946
let missing_comments = match rewrite_missing_comment(comments_span, cond_shape, context)
{
None => "".to_owned(),
Some(comment) if self.connector.is_empty() || comment.is_empty() => comment,
// Handle same-line block comments:
// if let Some(foo) = /*bar*/ baz { ... }
// if let Some(ref /*def*/ mut /*abc*/ state)...
Some(comment)
if !comment_style(&comment, false).is_line_comment()
&& !comment.contains('\n') =>
{
format!(" {}", comment)
}
// Handle sequence of multiple inline comments:
// if let Some(n) =
// // this is a test comment
// // with another
// foo { .... }
Some(_) => {
let newline = &cond_shape
.indent
.block_indent(context.config)
.to_string_with_newline(context.config);
let shape = pat_shape.block_indent(context.config.tab_spaces());
let comment = format!(
"{}{}",
newline,
rewrite_missing_comment(comments_span, shape, context)?,
);
let lhs = format!("{}{}{}{}", matcher, pat_string, self.connector, comment);
let orig_rhs = Some(format!("{}{}", newline, expr.rewrite(context, shape)?));
let rhs = choose_rhs(
context,
expr,
cond_shape,
orig_rhs,
RhsTactics::Default,
true,
)?;
return Some(format!("{}{}", lhs, rhs));
}
};
let result = format!(
"{}{}{}{}",
matcher, pat_string, self.connector, missing_comments
return rewrite_assign_rhs_with_comments(
context,
&format!("{}{}{}", matcher, pat_string, self.connector),
expr,
cond_shape,
RhsTactics::Default,
comments_span,
true,
);
return rewrite_assign_rhs(context, result, expr, cond_shape);
Copy link
Member

Choose a reason for hiding this comment

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

This seems like it could be extracted out into a separate PR, since it's largely just reusing a comparatively new helper and cleaning up some more complex code written before said helper.

If this can't be landed/merged independently, please let me know why, otherwise I'd ask that you pull this change out into a separate PR so that we can go ahead and push that through (understand that this PR may need/want that separate one to get merged first)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created PR #4626 with this change. This is the only change from this PR that does not depend on the other changes.

shape,
true,
)
.unwrap()
Copy link
Member

Choose a reason for hiding this comment

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

Please be sure to use the ? operator instead of explicitly unwrapping in functions that return an Option

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Changed unwrap() to ? in 3 places.

{
}
{}
Copy link
Member

Choose a reason for hiding this comment

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

this is a bit concerning from a stability perspective, could you explain why your proposed changes are modifying the emitted formatting in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That happened when formatting the line:

'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_valu{}

The { is in column 101, so it is moved to the next line. In the original code, rewrite_cond() returns used_width that is calculated based on the length of the code before the {. The line break before the '{' is added only later when calling rewrite_block_with_visitor() with a shape based on used_width. It seems that when rewrite_block_with_visitor() determines that the opening { should start a new line, it also does that for the closing }. This is the reason why in the original code the } is in a new line.

The change I did already adds the new-line indent before calculating used_width, so the shape received by rewrite_block_with_visitor() allows it to keep the the { in the same line and therefore the '}` does not start a new line.

Note that the difference is only when the {} block is empty and when the '{` starts a new line. E.g., the formatting of the following code:

fn main() {
	'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_valueLong{}
	'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_value{ x }
}

Is the same when using both the original and modified code:

fn main() {
    'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value
        > some_other_valueLong
    {}
    'a: while loooooooooooooooooooooooooooooooooong_variable_name + another_value > some_other_value
    {
        x
    }
}

@calebcartwright calebcartwright changed the base branch from master to rustfmt-2.0.0-rc.2 April 30, 2021 00:22
@calebcartwright
Copy link
Member

Thanks again for this, but going to go ahead and close given both the age and 2.0 target. If this is revisited in the future it'd be best done against the current mainline/master branch of the released and supported versions of rustfmt.

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

Successfully merging this pull request may close these issues.

2 participants