mirror of https://github.com/astral-sh/ruff
Special `ExprTuple` formatting option for `for`-loops (#5175)
## Motivation
While black keeps parentheses nearly everywhere, the notable exception
is in the body of for loops:
```python
for (a, b) in x:
pass
```
becomes
```python
for a, b in x:
pass
```
This currently blocks #5163, which this PR should unblock.
## Solution
This changes the `ExprTuple` formatting option to include one additional
option that removes the parentheses when not using magic trailing comma
and not breaking. It is supposed to be used through
```rust
#[derive(Debug)]
struct ExprTupleWithoutParentheses<'a>(&'a Expr);
impl Format<PyFormatContext<'_>> for ExprTupleWithoutParentheses<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self.0 {
Expr::Tuple(expr_tuple) => expr_tuple
.format()
.with_options(TupleParentheses::StripInsideForLoop)
.fmt(f),
other => other.format().with_options(Parenthesize::IfBreaks).fmt(f),
}
}
}
```
## Testing
The for loop formatting isn't merged due to missing this (and i didn't
want to create more git weirdness across two people), but I've confirmed
that when applying this to while loops instead of for loops, then
```rust
write!(
f,
[
text("while"),
space(),
ExprTupleWithoutParentheses(test.as_ref()),
text(":"),
trailing_comments(trailing_condition_comments),
block_indent(&body.format())
]
)?;
```
makes
```python
while (a, b):
pass
while (
ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
b,
):
pass
while (a,b,):
pass
```
formatted as
```python
while a, b:
pass
while (
ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
b,
):
pass
while (
a,
b,
):
pass
```
This commit is contained in:
parent
9b5fb8f38f
commit
9419d3f9c8
|
|
@ -10,13 +10,48 @@ use ruff_formatter::formatter::Formatter;
|
||||||
use ruff_formatter::prelude::{
|
use ruff_formatter::prelude::{
|
||||||
block_indent, group, if_group_breaks, soft_block_indent, soft_line_break_or_space, text,
|
block_indent, group, if_group_breaks, soft_block_indent, soft_line_break_or_space, text,
|
||||||
};
|
};
|
||||||
use ruff_formatter::{format_args, write, Buffer, Format, FormatResult};
|
use ruff_formatter::{format_args, write, Buffer, Format, FormatResult, FormatRuleWithOptions};
|
||||||
use ruff_python_ast::prelude::{Expr, Ranged};
|
use ruff_python_ast::prelude::{Expr, Ranged};
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use rustpython_parser::ast::ExprTuple;
|
use rustpython_parser::ast::ExprTuple;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Debug, Default)]
|
||||||
|
pub enum TupleParentheses {
|
||||||
|
/// Effectively `None` in `Option<Parentheses>`
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Effectively `Some(Parentheses)` in `Option<Parentheses>`
|
||||||
|
Expr(Parentheses),
|
||||||
|
/// Handle the special case where we remove parentheses even if they were initially present
|
||||||
|
///
|
||||||
|
/// Normally, black keeps parentheses, but in the case of loops it formats
|
||||||
|
/// ```python
|
||||||
|
/// for (a, b) in x:
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
/// to
|
||||||
|
/// ```python
|
||||||
|
/// for a, b in x:
|
||||||
|
/// pass
|
||||||
|
/// ```
|
||||||
|
/// Black still does use parentheses in this position if the group breaks or magic trailing
|
||||||
|
/// comma is used.
|
||||||
|
StripInsideForLoop,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatExprTuple;
|
pub struct FormatExprTuple {
|
||||||
|
parentheses: TupleParentheses,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatRuleWithOptions<ExprTuple, PyFormatContext<'_>> for FormatExprTuple {
|
||||||
|
type Options = TupleParentheses;
|
||||||
|
|
||||||
|
fn with_options(mut self, options: Self::Options) -> Self {
|
||||||
|
self.parentheses = options;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||||
fn fmt_fields(&self, item: &ExprTuple, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &ExprTuple, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
|
@ -74,9 +109,14 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||||
&text(")"),
|
&text(")"),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
} else if is_parenthesized(*range, elts, f) {
|
} else if is_parenthesized(*range, elts, f)
|
||||||
// If the tuple has parentheses, keep them. Note that unlike other expr parentheses,
|
&& self.parentheses != TupleParentheses::StripInsideForLoop
|
||||||
// those are actually part of the range
|
{
|
||||||
|
// If the tuple has parentheses, we generally want to keep them. The exception are for
|
||||||
|
// loops, see `TupleParentheses::StripInsideForLoop` doc comment.
|
||||||
|
//
|
||||||
|
// Unlike other expression parentheses, tuple parentheses are part of the range of the
|
||||||
|
// tuple itself.
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[group(&format_args![
|
[group(&format_args![
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::comments::Comments;
|
use crate::comments::Comments;
|
||||||
use crate::context::NodeLevel;
|
use crate::context::NodeLevel;
|
||||||
|
use crate::expression::expr_tuple::TupleParentheses;
|
||||||
use crate::expression::parentheses::{NeedsParentheses, Parentheses, Parenthesize};
|
use crate::expression::parentheses::{NeedsParentheses, Parentheses, Parenthesize};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use ruff_formatter::{
|
use ruff_formatter::{
|
||||||
|
|
@ -85,7 +86,10 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
|
||||||
Expr::Starred(expr) => expr.format().fmt(f),
|
Expr::Starred(expr) => expr.format().fmt(f),
|
||||||
Expr::Name(expr) => expr.format().fmt(f),
|
Expr::Name(expr) => expr.format().fmt(f),
|
||||||
Expr::List(expr) => expr.format().fmt(f),
|
Expr::List(expr) => expr.format().fmt(f),
|
||||||
Expr::Tuple(expr) => expr.format().fmt(f),
|
Expr::Tuple(expr) => expr
|
||||||
|
.format()
|
||||||
|
.with_options(TupleParentheses::Expr(parentheses))
|
||||||
|
.fmt(f),
|
||||||
Expr::Slice(expr) => expr.format().fmt(f),
|
Expr::Slice(expr) => expr.format().fmt(f),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue