Format expr generator exp (#5804)

This commit is contained in:
David Szotten 2023-07-19 12:01:58 +01:00 committed by GitHub
parent cda90d071c
commit 5d68ad9008
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 224 additions and 271 deletions

View file

@ -0,0 +1,27 @@
(a for b in c)
# parens around generator expression not required
len(a for b in c)
# parens around generator expression required
sum((a for b in c), start=0)
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
f((1 for _ in a))
# make sure source parenthesis detection isn't fooled by these
f((1) for _ in (a))
# combination of the two above
f(((1) for _ in (a)))
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
len(
( # leading
a for b in c
# trailing
)
)

View file

@ -6,6 +6,7 @@ use ruff_python_ast::node::AnyNodeRef;
use ruff_python_whitespace::{SimpleTokenizer, TokenKind}; use ruff_python_whitespace::{SimpleTokenizer, TokenKind};
use crate::comments::dangling_comments; use crate::comments::dangling_comments;
use crate::expression::expr_generator_exp::GeneratorExpParentheses;
use crate::expression::parentheses::{ use crate::expression::parentheses::{
parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
}; };
@ -50,13 +51,23 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
let mut joiner = f.join_comma_separated(item.end()); let mut joiner = f.join_comma_separated(item.end());
match args.as_slice() { match args.as_slice() {
[argument] if keywords.is_empty() => { [argument] if keywords.is_empty() => {
let parentheses = match argument {
if is_single_argument_parenthesized(argument, item.end(), source) { Expr::GeneratorExp(generator_exp) => joiner.entry(
Parentheses::Always generator_exp,
} else { &generator_exp
Parentheses::Never .format()
}; .with_options(GeneratorExpParentheses::StripIfOnlyFunctionArg),
joiner.entry(argument, &argument.format().with_options(parentheses)); ),
other => {
let parentheses =
if is_single_argument_parenthesized(argument, item.end(), source) {
Parentheses::Always
} else {
Parentheses::Never
};
joiner.entry(other, &other.format().with_options(parentheses))
}
};
} }
arguments => { arguments => {
joiner joiner

View file

@ -1,21 +1,76 @@
use crate::context::PyFormatContext; use crate::context::PyFormatContext;
use crate::expression::parentheses::parenthesized;
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; use crate::prelude::*;
use ruff_formatter::{write, Buffer, FormatResult}; use crate::AsFormat;
use crate::{FormatNodeRule, PyFormatter};
use ruff_formatter::{format_args, write, Buffer, FormatResult, FormatRuleWithOptions};
use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::node::AnyNodeRef;
use rustpython_parser::ast::ExprGeneratorExp; use rustpython_parser::ast::ExprGeneratorExp;
#[derive(Eq, PartialEq, Debug, Default)]
pub enum GeneratorExpParentheses {
#[default]
Default,
// skip parens if the generator exp is the only argument to a function, e.g.
// ```python
// all(x for y in z)`
// ```
StripIfOnlyFunctionArg,
}
impl FormatRuleWithOptions<ExprGeneratorExp, PyFormatContext<'_>> for FormatExprGeneratorExp {
type Options = GeneratorExpParentheses;
fn with_options(mut self, options: Self::Options) -> Self {
self.parentheses = options;
self
}
}
#[derive(Default)] #[derive(Default)]
pub struct FormatExprGeneratorExp; pub struct FormatExprGeneratorExp {
parentheses: GeneratorExpParentheses,
}
impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp { impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp {
fn fmt_fields(&self, _item: &ExprGeneratorExp, f: &mut PyFormatter) -> FormatResult<()> { fn fmt_fields(&self, item: &ExprGeneratorExp, f: &mut PyFormatter) -> FormatResult<()> {
write!( let ExprGeneratorExp {
f, range: _,
[not_yet_implemented_custom_text( elt,
"(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])" generators,
)] } = item;
)
let joined = format_with(|f| {
f.join_with(soft_line_break_or_space())
.entries(generators.iter().formatted())
.finish()
});
if self.parentheses == GeneratorExpParentheses::StripIfOnlyFunctionArg {
write!(
f,
[
group(&elt.format()),
soft_line_break_or_space(),
group(&joined),
]
)
} else {
write!(
f,
[parenthesized(
"(",
&format_args!(
group(&elt.format()),
soft_line_break_or_space(),
group(&joined)
),
")"
)]
)
}
} }
} }

View file

@ -121,34 +121,20 @@ def something():
): ):
pass pass
@@ -59,26 +53,14 @@ @@ -60,11 +54,9 @@
else "this one is a little shorter"
) )
-generator_expression = ( generator_expression = (
- ( - (
- some_long_value_name_foo_bar_baz - some_long_value_name_foo_bar_baz
- if some_boolean_variable - if some_boolean_variable
- else some_fallback_value_foo_bar_baz - else some_fallback_value_foo_bar_baz
- ) - )
- for some_boolean_variable in some_iterable + some_long_value_name_foo_bar_baz
-) + if some_boolean_variable
+generator_expression = (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) + else some_fallback_value_foo_bar_baz
for some_boolean_variable in some_iterable
)
def limit_offset_sql(self, low_mark, high_mark):
"""Return LIMIT/OFFSET SQL clause."""
limit, offset = self._get_limit_offset_params(low_mark, high_mark)
return " ".join(
- sql
- for sql in (
- "LIMIT %d" % limit if limit else None,
- ("OFFSET %d" % offset) if offset else None,
- )
- if sql
+ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
)
``` ```
@ -210,14 +196,24 @@ nested = (
else "this one is a little shorter" else "this one is a little shorter"
) )
generator_expression = (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) generator_expression = (
some_long_value_name_foo_bar_baz
if some_boolean_variable
else some_fallback_value_foo_bar_baz
for some_boolean_variable in some_iterable
)
def limit_offset_sql(self, low_mark, high_mark): def limit_offset_sql(self, low_mark, high_mark):
"""Return LIMIT/OFFSET SQL clause.""" """Return LIMIT/OFFSET SQL clause."""
limit, offset = self._get_limit_offset_params(low_mark, high_mark) limit, offset = self._get_limit_offset_params(low_mark, high_mark)
return " ".join( return " ".join(
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) sql
for sql in (
"LIMIT %d" % limit if limit else None,
("OFFSET %d" % offset) if offset else None,
)
if sql
) )

View file

@ -134,7 +134,7 @@ with match() as match:
def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]: def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:
@@ -23,13 +23,11 @@ @@ -23,11 +23,7 @@
pygram.python_grammar, pygram.python_grammar,
] ]
@ -145,14 +145,9 @@ with match() as match:
- pass - pass
+ NOT_YET_IMPLEMENTED_StmtMatch + NOT_YET_IMPLEMENTED_StmtMatch
- if all(version.is_python2() for version in target_versions): if all(version.is_python2() for version in target_versions):
+ if all(
+ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
+ ):
# Python 2-only code, so try Python 2 grammars. # Python 2-only code, so try Python 2 grammars.
return [ @@ -41,13 +37,11 @@
# Python 2.7 with future print_function import
@@ -41,13 +39,11 @@
re.match() re.match()
match = a match = a
with match() as match: with match() as match:
@ -168,7 +163,7 @@ with match() as match:
self.assertIs(x, False) self.assertIs(x, False)
self.assertEqual(y, 0) self.assertEqual(y, 0)
self.assertIs(z, x) self.assertIs(z, x)
@@ -72,16 +68,12 @@ @@ -72,16 +66,12 @@
def test_patma_155(self): def test_patma_155(self):
x = 0 x = 0
y = None y = None
@ -187,7 +182,7 @@ with match() as match:
# At least one of the above branches must have been taken, because every Python # At least one of the above branches must have been taken, because every Python
# version has exactly one of the two 'ASYNC_*' flags # version has exactly one of the two 'ASYNC_*' flags
@@ -99,9 +91,9 @@ @@ -99,9 +89,9 @@
re.match() re.match()
match = a match = a
with match() as match: with match() as match:
@ -231,9 +226,7 @@ def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:
NOT_YET_IMPLEMENTED_StmtMatch NOT_YET_IMPLEMENTED_StmtMatch
if all( if all(version.is_python2() for version in target_versions):
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
):
# Python 2-only code, so try Python 2 grammars. # Python 2-only code, so try Python 2 grammars.
return [ return [
# Python 2.7 with future print_function import # Python 2.7 with future print_function import

View file

@ -27,7 +27,7 @@ f(x, (a := b + c for c in range(10)), y=z, **q)
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -1,15 +1,20 @@ @@ -1,7 +1,7 @@
# Unparenthesized walruses are now allowed in indices since Python 3.10. # Unparenthesized walruses are now allowed in indices since Python 3.10.
-x[a:=0] -x[a:=0]
-x[a:=0, b:=1] -x[a:=0, b:=1]
@ -37,25 +37,7 @@ f(x, (a := b + c for c in range(10)), y=z, **q)
+x[5, b := 0] +x[5, b := 0]
# Walruses are allowed inside generator expressions on function calls since 3.10. # Walruses are allowed inside generator expressions on function calls since 3.10.
-if any(match := pattern_error.match(s) for s in buffer): if any(match := pattern_error.match(s) for s in buffer):
+if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])):
if match.group(2) == data_not_available:
# Error OK to ignore.
pass
-f(a := b + c for c in range(10))
-f((a := b + c for c in range(10)), x)
-f(y=(a := b + c for c in range(10)))
-f(x, (a := b + c for c in range(10)), y=z, **q)
+f((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []))
+f((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []), x)
+f(y=(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []))
+f(
+ x,
+ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []),
+ y=z,
+ **q,
+)
``` ```
## Ruff Output ## Ruff Output
@ -67,20 +49,15 @@ x[a := 0, b := 1]
x[5, b := 0] x[5, b := 0]
# Walruses are allowed inside generator expressions on function calls since 3.10. # Walruses are allowed inside generator expressions on function calls since 3.10.
if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])): if any(match := pattern_error.match(s) for s in buffer):
if match.group(2) == data_not_available: if match.group(2) == data_not_available:
# Error OK to ignore. # Error OK to ignore.
pass pass
f((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])) f(a := b + c for c in range(10))
f((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []), x) f((a := b + c for c in range(10)), x)
f(y=(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])) f(y=(a := b + c for c in range(10)))
f( f(x, (a := b + c for c in range(10)), y=z, **q)
x,
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []),
y=z,
**q,
)
``` ```
## Black Output ## Black Output

View file

@ -1,142 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_37/python37.py
---
## Input
```py
#!/usr/bin/env python3.7
def f():
return (i * 2 async for i in arange(42))
def g():
return (
something_long * something_long
async for something_long in async_generator(with_an_argument)
)
async def func():
if test:
out_batched = [
i
async for i in aitertools._async_map(
self.async_inc, arange(8), batch_size=3
)
]
def awaited_generator_value(n):
return (await awaitable for awaitable in awaitable_list)
def make_arange(n):
return (i * 2 for i in range(n) if await wrap(i))
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -2,14 +2,11 @@
def f():
- return (i * 2 async for i in arange(42))
+ return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
def g():
- return (
- something_long * something_long
- async for something_long in async_generator(with_an_argument)
- )
+ return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
async def func():
@@ -23,8 +20,8 @@
def awaited_generator_value(n):
- return (await awaitable for awaitable in awaitable_list)
+ return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
def make_arange(n):
- return (i * 2 for i in range(n) if await wrap(i))
+ return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
```
## Ruff Output
```py
#!/usr/bin/env python3.7
def f():
return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
def g():
return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
async def func():
if test:
out_batched = [
i
async for i in aitertools._async_map(
self.async_inc, arange(8), batch_size=3
)
]
def awaited_generator_value(n):
return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
def make_arange(n):
return (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
```
## Black Output
```py
#!/usr/bin/env python3.7
def f():
return (i * 2 async for i in arange(42))
def g():
return (
something_long * something_long
async for something_long in async_generator(with_an_argument)
)
async def func():
if test:
out_batched = [
i
async for i in aitertools._async_map(
self.async_inc, arange(8), batch_size=3
)
]
def awaited_generator_value(n):
return (await awaitable for awaitable in awaitable_list)
def make_arange(n):
return (i * 2 for i in range(n) if await wrap(i))
```

View file

@ -83,12 +83,9 @@ while x := f(x):
x = (y := 0) x = (y := 0)
(z := (y := (x := 0))) (z := (y := (x := 0)))
(info := (name, phone, *rest)) (info := (name, phone, *rest))
@@ -31,9 +31,9 @@ @@ -33,7 +33,7 @@
len(lines := f.readlines())
foo(x := 3, cat="vector")
foo(cat=(category := "vector")) foo(cat=(category := "vector"))
-if any(len(longline := l) >= 100 for l in lines): if any(len(longline := l) >= 100 for l in lines):
+if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])):
print(longline) print(longline)
-if env_base := os.environ.get("PYTHONUSERBASE", None): -if env_base := os.environ.get("PYTHONUSERBASE", None):
+if (env_base := os.environ.get("PYTHONUSERBASE", None)): +if (env_base := os.environ.get("PYTHONUSERBASE", None)):
@ -143,7 +140,7 @@ x = (y := 0)
len(lines := f.readlines()) len(lines := f.readlines())
foo(x := 3, cat="vector") foo(x := 3, cat="vector")
foo(cat=(category := "vector")) foo(cat=(category := "vector"))
if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])): if any(len(longline := l) >= 100 for l in lines):
print(longline) print(longline)
if (env_base := os.environ.get("PYTHONUSERBASE", None)): if (env_base := os.environ.get("PYTHONUSERBASE", None)):
return env_base return env_base

View file

@ -363,21 +363,6 @@ last_call()
numpy[np.newaxis, :] numpy[np.newaxis, :]
(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
{"2.7": dead, "3.7": long_live or die_hard} {"2.7": dead, "3.7": long_live or die_hard}
@@ -181,10 +187,10 @@
(SomeName)
SomeName
(Good, Bad, Ugly)
-(i for i in (1, 2, 3))
-((i**2) for i in (1, 2, 3))
-((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
-(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
+(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
+(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
+(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
+(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])
(*starred,)
{
"id": "1",
@@ -208,24 +214,14 @@ @@ -208,24 +214,14 @@
what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
vars_to_remove vars_to_remove
@ -435,15 +420,6 @@ last_call()
assert not Test, "Short message" assert not Test, "Short message"
assert this is ComplexTest and not requirements.fit_in_a_single_line( assert this is ComplexTest and not requirements.fit_in_a_single_line(
force=False force=False
@@ -259,7 +255,7 @@
...
for y in ():
...
-for z in (i for i in (1, 2, 3)):
+for z in (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []):
...
for i in call():
...
@@ -328,13 +324,18 @@ @@ -328,13 +324,18 @@
): ):
return True return True
@ -670,10 +646,10 @@ numpy[np.newaxis, :]
(SomeName) (SomeName)
SomeName SomeName
(Good, Bad, Ugly) (Good, Bad, Ugly)
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) (i for i in (1, 2, 3))
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) ((i**2) for i in (1, 2, 3))
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) ((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) (((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred,) (*starred,)
{ {
"id": "1", "id": "1",
@ -738,7 +714,7 @@ for (x,) in (1,), (2,), (3,):
... ...
for y in (): for y in ():
... ...
for z in (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []): for z in (i for i in (1, 2, 3)):
... ...
for i in call(): for i in call():
... ...

View file

@ -43,7 +43,7 @@ ham[lower + offset : upper + offset]
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -4,19 +4,21 @@ @@ -4,19 +4,19 @@
slice[d::d] slice[d::d]
slice[0] slice[0]
slice[-1] slice[-1]
@ -63,16 +63,13 @@ ham[lower + offset : upper + offset]
+slice[lambda NOT_YET_IMPLEMENTED_lambda: True :, None::] +slice[lambda NOT_YET_IMPLEMENTED_lambda: True :, None::]
slice[1 or 2 : True and False] slice[1 or 2 : True and False]
slice[not so_simple : 1 < val <= 10] slice[not so_simple : 1 < val <= 10]
-slice[(1 for i in range(42)) : x] slice[(1 for i in range(42)) : x]
-slice[:: [i for i in range(42)]] -slice[:: [i for i in range(42)]]
+slice[
+ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) : x
+]
+slice[ :: [i for i in range(42)]] +slice[ :: [i for i in range(42)]]
async def f(): async def f():
@@ -27,5 +29,5 @@ @@ -27,5 +27,5 @@
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step] ham[lower:upper], ham[lower:upper:], ham[lower::step]
# ham[lower+offset : upper+offset] # ham[lower+offset : upper+offset]
@ -101,9 +98,7 @@ slice[lambda NOT_YET_IMPLEMENTED_lambda: True : lambda NOT_YET_IMPLEMENTED_lambd
slice[lambda NOT_YET_IMPLEMENTED_lambda: True :, None::] slice[lambda NOT_YET_IMPLEMENTED_lambda: True :, None::]
slice[1 or 2 : True and False] slice[1 or 2 : True and False]
slice[not so_simple : 1 < val <= 10] slice[not so_simple : 1 < val <= 10]
slice[ slice[(1 for i in range(42)) : x]
(NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) : x
]
slice[ :: [i for i in range(42)]] slice[ :: [i for i in range(42)]]

View file

@ -283,7 +283,7 @@ aaaaaaaaaaaaaa + [
] ]
( (
aaaaaaaaaaaaaa aaaaaaaaaaaaaa
+ (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) + (a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
) )
aaaaaaaaaaaaaa + { aaaaaaaaaaaaaa + {
a a

View file

@ -0,0 +1,68 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py
---
## Input
```py
(a for b in c)
# parens around generator expression not required
len(a for b in c)
# parens around generator expression required
sum((a for b in c), start=0)
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
f((1 for _ in a))
# make sure source parenthesis detection isn't fooled by these
f((1) for _ in (a))
# combination of the two above
f(((1) for _ in (a)))
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
len(
( # leading
a for b in c
# trailing
)
)
```
## Output
```py
(a for b in c)
# parens around generator expression not required
len(a for b in c)
# parens around generator expression required
sum((a for b in c), start=0)
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
f(1 for _ in a)
# make sure source parenthesis detection isn't fooled by these
f((1) for _ in (a))
# combination of the two above
f((1) for _ in (a))
# black keeps these atm, but intends to remove them in the future:
# https://github.com/psf/black/issues/2943
len(
# leading
a
for b in c
# trailing
)
```

View file

@ -163,7 +163,7 @@ with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
# currently unparsable by black: https://github.com/psf/black/issues/3678 # currently unparsable by black: https://github.com/psf/black/issues/3678
with (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []): with (name_2 for name_0 in name_4):
pass pass
with (a, *b): with (a, *b):
pass pass