-
Notifications
You must be signed in to change notification settings - Fork 13.3k
docs(style): add let-chain style rules #110568
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -485,8 +485,11 @@ self.pre_comment.as_ref().map_or( | |
|
||
### Control flow expressions | ||
|
||
This section covers `if`, `if let`, `loop`, `while`, `while let`, and `for` | ||
expressions. | ||
This section covers `for` and `loop` expressions, as well as `if` and `while` | ||
expressions with their sub-expression variants. This includes those with a | ||
single `let` sub-expression (i.e. `if let` and `while let`) | ||
as well as "let-chains": those with one or more `let` sub-expressions and | ||
one or more bool-type conditions (i.e. `if a && let Some(b) = c`). | ||
|
||
The keyword, any initial clauses, and the opening brace of the block should be | ||
on a single line. The usual rules for [block formatting](#blocks) should be | ||
|
@@ -512,10 +515,11 @@ if let ... { | |
} | ||
``` | ||
|
||
If the control line needs to be broken, then prefer to break before the `=` in | ||
`* let` expressions and before `in` in a `for` expression; the following line | ||
should be block indented. If the control line is broken for any reason, then the | ||
opening brace should be on its own line and not indented. Examples: | ||
If the control line needs to be broken, then prefer breaking before the `=` for any | ||
`let` sub-expression in an `if` or `while` expression that does not fit, | ||
and before `in` in a `for` expression; the following line should be block indented. | ||
If the control line is broken for any reason, then the opening brace should be on its | ||
own line and not indented. Examples: | ||
Comment on lines
+518
to
+522
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the aspects that's been a struggle for me, as I feel like there's some important missing text here even without the additions for let-chains. Specifically, what happens of the pattern itself ends up needing to be formatted across multiple lines? The rules for let statements cover this scenario, and I imagine (though didn't check) rustfmt would do the same for a non-chained I was tempted to create a new section that covered the |
||
|
||
```rust | ||
while let Some(foo) | ||
|
@@ -536,6 +540,70 @@ if a_long_expression | |
{ | ||
... | ||
} | ||
|
||
if let Some(a) = b | ||
&& another_long_expression | ||
|| a_third_long_expression | ||
{ | ||
... | ||
} | ||
|
||
if let Some(relatively_long_thing) | ||
= a_long_expression | ||
&& another_long_expression | ||
|| a_third_long_expression | ||
{ | ||
Comment on lines
+551
to
+555
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good case for breaking after the assignment operator (rustfmt's current, technically buggy behavior in these control flow expressions, as well as the current behavior and rules for statements). I feel this is technically consistent with current rules, but IMO it would benefit from having the different indentation/spacing for the |
||
... | ||
} | ||
|
||
if some_expr | ||
&& another_long_expression | ||
&& let Some(relatively_long_thing) | ||
= a_long_expression | ||
|| a_third_long_expression | ||
{ | ||
... | ||
} | ||
``` | ||
|
||
A let-chain control line is allowed to be formatted on a single line provided | ||
it only consists of two clauses, separated by `&&`, with the first being an | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'd explicitly documented the desire to limit this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks that's helpful. I think one consideration though is what constitutes valid in the style/formatting context. I.e. at what stage does this get rejected, will it cause the parser to error out prior to creating the AST or does it occur later? Is there a potential for the supported operators to change in the future? There's existing precedent within the Style Guide that covers "invalid" flavors of syntax, e.g.
Which I always assume was the case because rustfmt has to account for it so long as the AST can still be produced. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The validation is not done during parsing. It is in the AST validator (which IIRC, is done just after expansion). The RFC mentioned that |
||
`ident` (which can optionally be preceded by any number of unary prefix operators) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should change this to "ident or literal", so that we don't break after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style team meeting change/addition discussed: ident or lit |
||
and the second being a single-line `let` clause. Otherwise, | ||
the control line must be broken and formatted according to the above rules. For example: | ||
|
||
```rust | ||
if a && let Some(b) = foo() { | ||
// ... | ||
} | ||
|
||
let operator = if !from_hir_call && let Some(p) = parent { | ||
// ... | ||
} | ||
|
||
if a | ||
|| let Some(b) = foo() | ||
Comment on lines
+584
to
+585
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example referenced above. I do not like the way this looks |
||
{ | ||
// .. | ||
} | ||
|
||
if let Some(b) = foo() | ||
&& a | ||
{ | ||
// .. | ||
} | ||
|
||
if foo() | ||
&& let Some(b) = bar | ||
{ | ||
// ... | ||
} | ||
|
||
if gen_pos != GenericArgPosition::Type | ||
&& let Some(b) = gen_args.bindings.first() | ||
{ | ||
// .. | ||
} | ||
``` | ||
|
||
Where the initial clause is multi-lined and ends with one or more closing | ||
|
@@ -554,7 +622,6 @@ if !self.config.file_lines().intersects( | |
} | ||
``` | ||
|
||
|
||
#### Single line `if else` | ||
|
||
Formatters may place an `if else` or `if let else` on a single line if it occurs | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was ambivalent and self-argumentative around whether it would be more fluid to incorporate the new rules into the existing narratives or to create a net-new section. I eventually landed on the former as I thought it read more naturally and allowed for greater reuse of existing rule prose.
I do not feel strongly, and could be convinced otherwise pretty easily