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

style: How to format multi-line if condition with clang-format? #21735

Closed
maflcko opened this issue Apr 20, 2021 · 18 comments
Closed

style: How to format multi-line if condition with clang-format? #21735

maflcko opened this issue Apr 20, 2021 · 18 comments

Comments

@maflcko
Copy link
Member

maflcko commented Apr 20, 2021

Usually clang-format is overall producing a nice formatting style. However, it doesn't handle multi-line if conditions well.

Currently it will put the condition as well as the true-block on the same indentation. Making them appear to be one block. This isn't nice to the eye and I wouldn't be surprised if it lead to bugs eventually.

Example:

    if (IsThisTrue() ||
        IsThatTrue()) {
        DoThis;
        DoThat;
    }
@amitiuttarwar
Copy link
Contributor

agree that this is aesthetically misleading. to brainstorm,

what do you think about this for when the conditionals are short enough:

if (IsThisTrue() || IsThatTrue()) {
        DoThis;
        DoThat;
    }

and this for when they are longer:

if (IsThisTrue() 
 || IsThatTrue()) {
        DoThis;
        DoThat;
    }

@maflcko
Copy link
Member Author

maflcko commented Apr 20, 2021

Yeah, I think single-line if-conditions are already fine. And for multi-line conditions I am looking for something that works with clang-format, ideally. Right now I manually put the opening { on a new line, which will be undone the next time I call clang-format.

One hack would be to use double (()) around the condition.

    if ((nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE ||
         nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE)) {
        BOOST_CHECK_EQUAL(min_activation_height, 0);
        return;
    }

@kristapsk
Copy link
Contributor

For multi-line if conditions I personally prefer this way, where I add newline after first ( and before last ), so that it's better division between last line of condition and first line of code in if block.

    if (
        nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE ||
        nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE
    ) {
        BOOST_CHECK_EQUAL(min_activation_height, 0);
        return;
    }

But have no idea how that works with clang-format.

@maflcko maflcko changed the title style: How to format multi-line if condition? style: How to format multi-line if condition with clang-format? Apr 20, 2021
@jnewbery
Copy link
Contributor

How about placing the boolean operator at the start of the new line:

    if (IsThisTrue()
        || IsThatTrue()) {
        DoThis;
        DoThat;
    }

This is recommended by the GNU coding standards (https://www.gnu.org/prep/standards/html_node/Formatting.html):

When you split an expression into multiple lines, split it before an operator, not after one. Here is the right way:

if (foo_this_is_long && bar > win (x, y, z)
    && remaining_condition)

Try to avoid having two operators of different precedence at the same level of indentation. For example, don’t write this:

mode = (inmode[j] == VOIDmode
        || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])
        ? outmode[j] : inmode[j]);

Instead, use extra parentheses so that the indentation shows the nesting:

mode = ((inmode[j] == VOIDmode
         || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])))
        ? outmode[j] : inmode[j]);

[...]

Google C++ standards just say "When you have a boolean expression that is longer than the standard line length, be consistent in how you break up the lines." (https://google.github.io/styleguide/cppguide.html#Boolean_Expressions)

A bit more discussion here: https://news.ycombinator.com/item?id=7975386

@maflcko
Copy link
Member Author

maflcko commented Apr 26, 2021

That could be set via BreakBeforeBinaryOperators in clang-format

@jonatack
Copy link
Contributor

This is recommended by the GNU coding standards (https://www.gnu.org/prep/standards/html_node/Formatting.html):

Agree (after first attempting to write things in a way that avoids the need for multi-line conditionals).

@ajtowns
Copy link
Contributor

ajtowns commented May 13, 2021

Has open-brace-on-new-line already been ruled out for multi-line conditions?

if (short_condition) trivial_action();

if (short_condition) {
    complex();
    action();
}

if (one_complicated_condition
    && another_complicated_condition)
{
    trivial_or_complex_action();
}

Specifying BraceWrapping: AfterControlStatement: MultiLine stops clang-format from undoing that style, I think -- but we'd need to set ColumnLimit: to something for it to actually stop you from putting an open brace at the end of a multiline if.

@martinus
Copy link
Contributor

Right now I manually put the opening { on a new line, which will be undone the next time I call clang-format.

Another hack would be to add // at the end of the if. In combination with BreakBeforeBinaryOperators: NonAssignment clang-format wouldn't change this any more:

if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE
    || nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) //
{
    BOOST_CHECK_EQUAL(min_activation_height, 0);
    return;
}

I couldn't get BraceWrapping: AfterControlStatement working with that.

@vasild
Copy link
Contributor

vasild commented Jul 29, 2021

Isn't this discussion irrelevant with the current unlimited line length (ColumnLimit: 0)? For me, no matter how I break the line manually, clang-format joins all the lines into a single line (unlimitedly long one). Also it never decides to break a long line.

Otherwise, I like @jnewbery's suggestion above. BreakBeforeBinaryOperators: true does just that.

@maflcko
Copy link
Member Author

maflcko commented Oct 7, 2021

Has open-brace-on-new-line already been ruled out for multi-line conditions?

No. I wasn't aware of this option. Thanks for suggesting. Fixed in #23216.

For me, no matter how I break the line manually, clang-format joins all the lines into a single line (unlimitedly long one).

I can't reproduce. Does this happen with the example "dirty" diff in #23216 (comment) ? Which version of clang-format are you using?

@vasild
Copy link
Contributor

vasild commented Oct 7, 2021

I played a bit with this. It looks like clang-format would join lines only sometimes. Not always, as I was thinking before. For example, with the current config, the following:

    // Example1
    if (m_connman.ShouldRunInactivityChecks(node_to) && peer.m_ping_nonce_sent 
        && now > peer.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL}) {

is joined into a single line:

    if (m_connman.ShouldRunInactivityChecks(node_to) && peer.m_ping_nonce_sent && now > peer.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL}) {

But this remains unchanged:

    // Example2
    if (m_connman.ShouldRunInactivityChecks(node_to) && peer.m_ping_nonce_sent &&
        now > peer.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL}) {

I guess this is due to BreakBeforeBinaryOperators: false. If I change that to true, then Example1 remains unchanged while Example2 is joined into a single line.

Does this happen with the example "dirty" diff...

No, because it breaks after &&.

Which version of clang-format are you using?

Tried with 12 and 14.

It looks like manually inserted line breaks, if they do not violate some rules, are respected and not removed. If they violate e.g. BreakBeforeBinaryOperators then they are removed and lines joined.

@maflcko
Copy link
Member Author

maflcko commented Oct 8, 2021

Specifying BraceWrapping: AfterControlStatement: MultiLine stops clang-format from undoing that style, I think -- but we'd need to set ColumnLimit: to something for it to actually stop you from putting an open brace at the end of a multiline if.

Isn't this discussion irrelevant with the current unlimited line length (ColumnLimit: 0)?

Right, the column limit needs to be set first before looking into this.

@vasild
Copy link
Contributor

vasild commented Oct 8, 2021

FWIW, I run a shell wrapper of clang-format that:

  • Changes ColumnLimit from 0 to 100 in src/.clang-format
  • Runs the real clang-format
  • Restores the original src/.clang-format (so that I don't commit it accidentally)

The unlimited line length creates too many problems for me, so I solve it locally. Have been afraid to raise the topic publicly 🔥

@maflcko maflcko closed this as completed Oct 21, 2021
@maflcko
Copy link
Member Author

maflcko commented Nov 14, 2021

FWIW, I run a shell wrapper of clang-format that:

Same.

This is the patch I use. (150 is also the limit on the GitHub web view)

diff --git a/src/.clang-format b/src/.clang-format
index 791b3b8f9f..8da3baf34a 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -17,10 +17,11 @@ BreakBeforeBinaryOperators: false
 BreakBeforeBraces: Custom
 BraceWrapping:
   AfterClass: true
+  AfterControlStatement: MultiLine
   AfterFunction: true
 BreakBeforeTernaryOperators: false
 BreakConstructorInitializersBeforeComma: false
-ColumnLimit:     0
+ColumnLimit:     150
 CommentPragmas:  '^ IWYU pragma:'
 ConstructorInitializerAllOnOneLineOrOnePerLine: false
 ConstructorInitializerIndentWidth: 4

@martinus
Copy link
Contributor

150 is also the limit on the GitHub web view

I'm pretty sure that GitHub's limit depends on your monitor and fonts used. For me a diff on github can have 156 characters. I've created a little project to see the line lengths: https://github.com/martinus/github-character-length

I would very much appreciate it if we could add a reasonable default ColumnLimit to the .clang-format. I'm fine with anything in the range 80-150. I usually use around 130 or so.

@maflcko
Copy link
Member Author

maflcko commented Nov 14, 2021

Ok, I meant the GitHub line limit with an unlimited monitor. Locally I get 153 chars for https://github.com/martinus/github-character-length/pull/2/files

@vasild
Copy link
Contributor

vasild commented Nov 15, 2021

For me:

Diff, unified view: 129
Diff, split view: 62
Comment in PR: 80

but yes, it quickly changes if I zoom in/out (ctrl + +/-). I think Github's sh**y web UI, that could as well change, shouldn't be a deciding factor for ColumnLimit in .clang-format.

@maflcko
Copy link
Member Author

maflcko commented Nov 15, 2021

It shouldn't be important what the exact limit is, as it is only used to format touched code, not reformat the whole codebase. If there is a reason, it can always be adjusted. Though, initially it shouldn't be reduced too much, since it is currently unlimited.

@bitcoin bitcoin locked and limited conversation to collaborators Nov 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants