mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
Format named expressions (walrus operator) (#5642)
## Summary Format named expressions (walrus operator) such a `value := f()`. Unlike tuples, named expression parentheses are not part of the range even when mandatory, so mapping optional parentheses to always gives us decent formatting without implementing all [PEP 572](https://peps.python.org/pep-0572/) rules on when we need parentheses where other expressions wouldn't. We might want to revisit this decision later and implement special cases, but for now this gives us what we need. ## Test Plan black fixtures, i added some fixtures and checked django and cpython for stability. Closes #5613
This commit is contained in:
parent
1e894f328c
commit
bd8f65814c
8 changed files with 152 additions and 318 deletions
|
@ -32,9 +32,9 @@ f(x, (a := b + c for c in range(10)), y=z, **q)
|
|||
-x[a:=0]
|
||||
-x[a:=0, b:=1]
|
||||
-x[5, b:=0]
|
||||
+x[NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
+x[NOT_YET_IMPLEMENTED_ExprNamedExpr, NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
+x[5, NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
+x[a := 0]
|
||||
+x[a := 0, b := 1]
|
||||
+x[5, b := 0]
|
||||
|
||||
# Walruses are allowed inside generator expressions on function calls since 3.10.
|
||||
-if any(match := pattern_error.match(s) for s in buffer):
|
||||
|
@ -62,9 +62,9 @@ f(x, (a := b + c for c in range(10)), y=z, **q)
|
|||
|
||||
```py
|
||||
# Unparenthesized walruses are now allowed in indices since Python 3.10.
|
||||
x[NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
x[NOT_YET_IMPLEMENTED_ExprNamedExpr, NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
x[5, NOT_YET_IMPLEMENTED_ExprNamedExpr]
|
||||
x[a := 0]
|
||||
x[a := 0, b := 1]
|
||||
x[5, b := 0]
|
||||
|
||||
# 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 [])):
|
||||
|
|
|
@ -59,36 +59,20 @@ while x := f(x):
|
|||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,47 +1,47 @@
|
||||
-(a := 1)
|
||||
-(a := a)
|
||||
-if (match := pattern.search(data)) is None:
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+if (NOT_YET_IMPLEMENTED_ExprNamedExpr) is None:
|
||||
@@ -2,10 +2,10 @@
|
||||
(a := a)
|
||||
if (match := pattern.search(data)) is None:
|
||||
pass
|
||||
-if match := pattern.search(data):
|
||||
+if NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
+if (match := pattern.search(data)):
|
||||
pass
|
||||
-[y := f(x), y**2, y**3]
|
||||
[y := f(x), y**2, y**3]
|
||||
-filtered_data = [y for x in data if (y := f(x)) is None]
|
||||
-(y := f(x))
|
||||
-y0 = (y1 := f(x))
|
||||
-foo(x=(y := f(x)))
|
||||
+[NOT_YET_IMPLEMENTED_ExprNamedExpr, y**2, y**3]
|
||||
+filtered_data = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []]
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+y0 = NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
+foo(x=(NOT_YET_IMPLEMENTED_ExprNamedExpr))
|
||||
|
||||
|
||||
-def foo(answer=(p := 42)):
|
||||
+def foo(answer=(NOT_YET_IMPLEMENTED_ExprNamedExpr)):
|
||||
pass
|
||||
|
||||
|
||||
-def foo(answer: (p := 42) = 5):
|
||||
+def foo(answer: (NOT_YET_IMPLEMENTED_ExprNamedExpr) = 5):
|
||||
(y := f(x))
|
||||
y0 = (y1 := f(x))
|
||||
foo(x=(y := f(x)))
|
||||
@@ -19,29 +19,29 @@
|
||||
pass
|
||||
|
||||
|
||||
|
@ -96,99 +80,89 @@ while x := f(x):
|
|||
-(x := lambda: 1)
|
||||
-(x := lambda: (y := 1))
|
||||
-lambda line: (m := re.match(pattern, line)) and m.group(1)
|
||||
-x = (y := 0)
|
||||
-(z := (y := (x := 0)))
|
||||
+lambda x: True
|
||||
+(x := lambda x: True)
|
||||
+(x := lambda x: True)
|
||||
+lambda x: True
|
||||
x = (y := 0)
|
||||
(z := (y := (x := 0)))
|
||||
-(info := (name, phone, *rest))
|
||||
-(x := 1, 2)
|
||||
-(total := total + tax)
|
||||
-len(lines := f.readlines())
|
||||
-foo(x := 3, cat="vector")
|
||||
-foo(cat=(category := "vector"))
|
||||
+(info := (name, phone, *NOT_YET_IMPLEMENTED_ExprStarred))
|
||||
(x := 1, 2)
|
||||
(total := total + tax)
|
||||
len(lines := f.readlines())
|
||||
foo(x := 3, cat="vector")
|
||||
foo(cat=(category := "vector"))
|
||||
-if any(len(longline := l) >= 100 for l in lines):
|
||||
+lambda x: True
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+lambda x: True
|
||||
+x = NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr, 2)
|
||||
+(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+len(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
+foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, cat="vector")
|
||||
+foo(cat=(NOT_YET_IMPLEMENTED_ExprNamedExpr))
|
||||
+if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])):
|
||||
print(longline)
|
||||
-if env_base := os.environ.get("PYTHONUSERBASE", None):
|
||||
+if NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
+if (env_base := os.environ.get("PYTHONUSERBASE", None)):
|
||||
return env_base
|
||||
-if self._is_special and (ans := self._check_nans(context=context)):
|
||||
+if self._is_special and (NOT_YET_IMPLEMENTED_ExprNamedExpr):
|
||||
if self._is_special and (ans := self._check_nans(context=context)):
|
||||
return ans
|
||||
-foo(b := 2, a=1)
|
||||
foo(b := 2, a=1)
|
||||
-foo((b := 2), a=1)
|
||||
-foo(c=(b := 2), a=1)
|
||||
+foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, a=1)
|
||||
+foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, a=1)
|
||||
+foo(c=(NOT_YET_IMPLEMENTED_ExprNamedExpr), a=1)
|
||||
+foo(b := 2, a=1)
|
||||
foo(c=(b := 2), a=1)
|
||||
|
||||
-while x := f(x):
|
||||
+while NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
+while (x := f(x)):
|
||||
pass
|
||||
-while x := f(x):
|
||||
+while NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
+while (x := f(x)):
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```py
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
if (NOT_YET_IMPLEMENTED_ExprNamedExpr) is None:
|
||||
(a := 1)
|
||||
(a := a)
|
||||
if (match := pattern.search(data)) is None:
|
||||
pass
|
||||
if NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
if (match := pattern.search(data)):
|
||||
pass
|
||||
[NOT_YET_IMPLEMENTED_ExprNamedExpr, y**2, y**3]
|
||||
[y := f(x), y**2, y**3]
|
||||
filtered_data = [NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []]
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
y0 = NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
foo(x=(NOT_YET_IMPLEMENTED_ExprNamedExpr))
|
||||
(y := f(x))
|
||||
y0 = (y1 := f(x))
|
||||
foo(x=(y := f(x)))
|
||||
|
||||
|
||||
def foo(answer=(NOT_YET_IMPLEMENTED_ExprNamedExpr)):
|
||||
def foo(answer=(p := 42)):
|
||||
pass
|
||||
|
||||
|
||||
def foo(answer: (NOT_YET_IMPLEMENTED_ExprNamedExpr) = 5):
|
||||
def foo(answer: (p := 42) = 5):
|
||||
pass
|
||||
|
||||
|
||||
lambda x: True
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
(x := lambda x: True)
|
||||
(x := lambda x: True)
|
||||
lambda x: True
|
||||
x = NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr, 2)
|
||||
(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
len(NOT_YET_IMPLEMENTED_ExprNamedExpr)
|
||||
foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, cat="vector")
|
||||
foo(cat=(NOT_YET_IMPLEMENTED_ExprNamedExpr))
|
||||
x = (y := 0)
|
||||
(z := (y := (x := 0)))
|
||||
(info := (name, phone, *NOT_YET_IMPLEMENTED_ExprStarred))
|
||||
(x := 1, 2)
|
||||
(total := total + tax)
|
||||
len(lines := f.readlines())
|
||||
foo(x := 3, cat="vector")
|
||||
foo(cat=(category := "vector"))
|
||||
if any((NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in [])):
|
||||
print(longline)
|
||||
if NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
if (env_base := os.environ.get("PYTHONUSERBASE", None)):
|
||||
return env_base
|
||||
if self._is_special and (NOT_YET_IMPLEMENTED_ExprNamedExpr):
|
||||
if self._is_special and (ans := self._check_nans(context=context)):
|
||||
return ans
|
||||
foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, a=1)
|
||||
foo(NOT_YET_IMPLEMENTED_ExprNamedExpr, a=1)
|
||||
foo(c=(NOT_YET_IMPLEMENTED_ExprNamedExpr), a=1)
|
||||
foo(b := 2, a=1)
|
||||
foo(b := 2, a=1)
|
||||
foo(c=(b := 2), a=1)
|
||||
|
||||
while NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
while (x := f(x)):
|
||||
pass
|
||||
while NOT_YET_IMPLEMENTED_ExprNamedExpr:
|
||||
while (x := f(x)):
|
||||
pass
|
||||
```
|
||||
|
||||
|
|
|
@ -22,15 +22,13 @@ x[(a := 1), (b := 3)]
|
|||
@@ -1,7 +1,7 @@
|
||||
# Unparenthesized walruses are now allowed in set literals & set comprehensions
|
||||
# since Python 3.9
|
||||
-{x := 1, 2, 3}
|
||||
{x := 1, 2, 3}
|
||||
-{x4 := x**5 for x in range(7)}
|
||||
+{NOT_YET_IMPLEMENTED_ExprNamedExpr, 2, 3}
|
||||
+{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set}
|
||||
# We better not remove the parentheses here (since it's a 3.10 feature)
|
||||
-x[(a := 1)]
|
||||
x[(a := 1)]
|
||||
-x[(a := 1), (b := 3)]
|
||||
+x[(NOT_YET_IMPLEMENTED_ExprNamedExpr)]
|
||||
+x[((NOT_YET_IMPLEMENTED_ExprNamedExpr), (NOT_YET_IMPLEMENTED_ExprNamedExpr))]
|
||||
+x[((a := 1), (b := 3))]
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
@ -38,11 +36,11 @@ x[(a := 1), (b := 3)]
|
|||
```py
|
||||
# Unparenthesized walruses are now allowed in set literals & set comprehensions
|
||||
# since Python 3.9
|
||||
{NOT_YET_IMPLEMENTED_ExprNamedExpr, 2, 3}
|
||||
{x := 1, 2, 3}
|
||||
{NOT_IMPLEMENTED_set_value for value in NOT_IMPLEMENTED_set}
|
||||
# We better not remove the parentheses here (since it's a 3.10 feature)
|
||||
x[(NOT_YET_IMPLEMENTED_ExprNamedExpr)]
|
||||
x[((NOT_YET_IMPLEMENTED_ExprNamedExpr), (NOT_YET_IMPLEMENTED_ExprNamedExpr))]
|
||||
x[(a := 1)]
|
||||
x[((a := 1), (b := 3))]
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
|
|
@ -32,14 +32,17 @@ def f():
|
|||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
@@ -13,8 +12,6 @@
|
||||
@@ -13,8 +12,10 @@
|
||||
...
|
||||
|
||||
|
||||
-@extremely_long_variable_name_that_doesnt_fit := complex.expression(
|
||||
- with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
|
||||
-)
|
||||
+@NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
+@(
|
||||
+ extremely_long_variable_name_that_doesnt_fit := complex.expression(
|
||||
+ with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
|
||||
+ )
|
||||
)
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
@ -61,7 +64,11 @@ def f():
|
|||
...
|
||||
|
||||
|
||||
@NOT_YET_IMPLEMENTED_ExprNamedExpr
|
||||
@(
|
||||
extremely_long_variable_name_that_doesnt_fit := complex.expression(
|
||||
with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
|
||||
)
|
||||
)
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_39/remove_with_brackets.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```py
|
||||
with (open("bla.txt")):
|
||||
pass
|
||||
|
||||
with (open("bla.txt")), (open("bla.txt")):
|
||||
pass
|
||||
|
||||
with (open("bla.txt") as f):
|
||||
pass
|
||||
|
||||
# Remove brackets within alias expression
|
||||
with (open("bla.txt")) as f:
|
||||
pass
|
||||
|
||||
# Remove brackets around one-line context managers
|
||||
with (open("bla.txt") as f, (open("x"))):
|
||||
pass
|
||||
|
||||
with ((open("bla.txt")) as f, open("x")):
|
||||
pass
|
||||
|
||||
with (CtxManager1() as example1, CtxManager2() as example2):
|
||||
...
|
||||
|
||||
# Brackets remain when using magic comma
|
||||
with (CtxManager1() as example1, CtxManager2() as example2,):
|
||||
...
|
||||
|
||||
# Brackets remain for multi-line context managers
|
||||
with (CtxManager1() as example1, CtxManager2() as example2, CtxManager2() as example2, CtxManager2() as example2, CtxManager2() as example2):
|
||||
...
|
||||
|
||||
# Don't touch assignment expressions
|
||||
with (y := open("./test.py")) as f:
|
||||
pass
|
||||
|
||||
# Deeply nested examples
|
||||
# N.B. Multiple brackets are only possible
|
||||
# around the context manager itself.
|
||||
# Only one brackets is allowed around the
|
||||
# alias expression or comma-delimited context managers.
|
||||
with (((open("bla.txt")))):
|
||||
pass
|
||||
|
||||
with (((open("bla.txt")))), (((open("bla.txt")))):
|
||||
pass
|
||||
|
||||
with (((open("bla.txt")))) as f:
|
||||
pass
|
||||
|
||||
with ((((open("bla.txt")))) as f):
|
||||
pass
|
||||
|
||||
with ((((CtxManager1()))) as example1, (((CtxManager2()))) as example2):
|
||||
...
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -39,7 +39,7 @@
|
||||
...
|
||||
|
||||
# Don't touch assignment expressions
|
||||
-with (y := open("./test.py")) as f:
|
||||
+with NOT_YET_IMPLEMENTED_ExprNamedExpr as f:
|
||||
pass
|
||||
|
||||
# Deeply nested examples
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```py
|
||||
with open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt"), open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
# Remove brackets within alias expression
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
# Remove brackets around one-line context managers
|
||||
with open("bla.txt") as f, open("x"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f, open("x"):
|
||||
pass
|
||||
|
||||
with CtxManager1() as example1, CtxManager2() as example2:
|
||||
...
|
||||
|
||||
# Brackets remain when using magic comma
|
||||
with (
|
||||
CtxManager1() as example1,
|
||||
CtxManager2() as example2,
|
||||
):
|
||||
...
|
||||
|
||||
# Brackets remain for multi-line context managers
|
||||
with (
|
||||
CtxManager1() as example1,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
):
|
||||
...
|
||||
|
||||
# Don't touch assignment expressions
|
||||
with NOT_YET_IMPLEMENTED_ExprNamedExpr as f:
|
||||
pass
|
||||
|
||||
# Deeply nested examples
|
||||
# N.B. Multiple brackets are only possible
|
||||
# around the context manager itself.
|
||||
# Only one brackets is allowed around the
|
||||
# alias expression or comma-delimited context managers.
|
||||
with open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt"), open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
with CtxManager1() as example1, CtxManager2() as example2:
|
||||
...
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```py
|
||||
with open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt"), open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
# Remove brackets within alias expression
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
# Remove brackets around one-line context managers
|
||||
with open("bla.txt") as f, open("x"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f, open("x"):
|
||||
pass
|
||||
|
||||
with CtxManager1() as example1, CtxManager2() as example2:
|
||||
...
|
||||
|
||||
# Brackets remain when using magic comma
|
||||
with (
|
||||
CtxManager1() as example1,
|
||||
CtxManager2() as example2,
|
||||
):
|
||||
...
|
||||
|
||||
# Brackets remain for multi-line context managers
|
||||
with (
|
||||
CtxManager1() as example1,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
CtxManager2() as example2,
|
||||
):
|
||||
...
|
||||
|
||||
# Don't touch assignment expressions
|
||||
with (y := open("./test.py")) as f:
|
||||
pass
|
||||
|
||||
# Deeply nested examples
|
||||
# N.B. Multiple brackets are only possible
|
||||
# around the context manager itself.
|
||||
# Only one brackets is allowed around the
|
||||
# alias expression or comma-delimited context managers.
|
||||
with open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt"), open("bla.txt"):
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
with open("bla.txt") as f:
|
||||
pass
|
||||
|
||||
with CtxManager1() as example1, CtxManager2() as example2:
|
||||
...
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/named_expr.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
y = 1
|
||||
|
||||
if (
|
||||
# 1
|
||||
x # 2
|
||||
:= # 3
|
||||
y # 4
|
||||
):
|
||||
pass
|
||||
|
||||
y0 = (y1 := f(x))
|
||||
|
||||
f(x:=y, z=True)
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
y = 1
|
||||
|
||||
if (
|
||||
# 1
|
||||
x := y # 2 # 3 # 4
|
||||
):
|
||||
pass
|
||||
|
||||
y0 = (y1 := f(x))
|
||||
|
||||
f(x := y, z=True)
|
||||
```
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue