diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP007.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP007.py index 56591e565c..287652b030 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP007.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP007.py @@ -27,6 +27,14 @@ def f(x: typing.Union[(str, int), float]) -> None: ... +def f(x: typing.Union[(int,)]) -> None: + ... + + +def f(x: typing.Union[()]) -> None: + ... + + def f(x: "Union[str, int, Union[float, bytes]]") -> None: ... diff --git a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs index 819d018c47..b1515b410e 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -1,3 +1,4 @@ +use itertools::Either::{Left, Right}; use itertools::Itertools; use rustpython_parser::ast::{self, Expr, Ranged}; @@ -63,7 +64,7 @@ pub(crate) fn use_pep604_annotation( Pep604Operator::Optional => { let mut diagnostic = Diagnostic::new(NonPEP604Annotation, expr.range()); if fixable && checker.patch(diagnostic.kind.rule()) { - diagnostic.set_fix(Fix::manual(Edit::range_replacement( + diagnostic.set_fix(Fix::suggested(Edit::range_replacement( optional(slice, checker.locator), expr.range(), ))); @@ -78,14 +79,14 @@ pub(crate) fn use_pep604_annotation( // Invalid type annotation. } Expr::Tuple(ast::ExprTuple { elts, .. }) => { - diagnostic.set_fix(Fix::manual(Edit::range_replacement( + diagnostic.set_fix(Fix::suggested(Edit::range_replacement( union(elts, checker.locator), expr.range(), ))); } _ => { // Single argument. - diagnostic.set_fix(Fix::manual(Edit::range_replacement( + diagnostic.set_fix(Fix::suggested(Edit::range_replacement( checker.locator.slice(slice.range()).to_string(), expr.range(), ))); @@ -97,12 +98,23 @@ pub(crate) fn use_pep604_annotation( } } +/// Format the expression as a PEP 604-style optional. fn optional(expr: &Expr, locator: &Locator) -> String { format!("{} | None", locator.slice(expr.range())) } +/// Format the expressions as a PEP 604-style union. fn union(elts: &[Expr], locator: &Locator) -> String { - elts.iter() - .map(|expr| locator.slice(expr.range())) - .join(" | ") + let mut elts = elts + .iter() + .flat_map(|expr| match expr { + Expr::Tuple(ast::ExprTuple { elts, .. }) => Left(elts.iter()), + _ => Right(std::iter::once(expr)), + }) + .peekable(); + if elts.peek().is_none() { + "()".to_string() + } else { + elts.map(|expr| locator.slice(expr.range())).join(" | ") + } } diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP007.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP007.py.snap index cd61499b3c..dc112cd0e0 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP007.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP007.py.snap @@ -9,7 +9,7 @@ UP007.py:6:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 3 3 | from typing import Union 4 4 | 5 5 | @@ -27,7 +27,7 @@ UP007.py:10:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 7 7 | ... 8 8 | 9 9 | @@ -45,7 +45,7 @@ UP007.py:14:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 11 11 | ... 12 12 | 13 13 | @@ -63,7 +63,7 @@ UP007.py:14:26: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 11 11 | ... 12 12 | 13 13 | @@ -81,7 +81,7 @@ UP007.py:18:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 15 15 | ... 16 16 | 17 17 | @@ -99,7 +99,7 @@ UP007.py:22:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 19 19 | ... 20 20 | 21 21 | @@ -117,127 +117,163 @@ UP007.py:26:10: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 23 23 | ... 24 24 | 25 25 | 26 |-def f(x: typing.Union[(str, int), float]) -> None: - 26 |+def f(x: (str, int) | float) -> None: + 26 |+def f(x: str | int | float) -> None: 27 27 | ... 28 28 | 29 29 | -UP007.py:30:11: UP007 [*] Use `X | Y` for type annotations +UP007.py:30:10: UP007 [*] Use `X | Y` for type annotations | -30 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +30 | def f(x: typing.Union[(int,)]) -> None: + | ^^^^^^^^^^^^^^^^^^^^ UP007 31 | ... | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 27 27 | ... 28 28 | 29 29 | -30 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: - 30 |+def f(x: "str | int | Union[float, bytes]") -> None: +30 |-def f(x: typing.Union[(int,)]) -> None: + 30 |+def f(x: int) -> None: 31 31 | ... 32 32 | 33 33 | -UP007.py:30:27: UP007 [*] Use `X | Y` for type annotations +UP007.py:34:10: UP007 [*] Use `X | Y` for type annotations | -30 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: - | ^^^^^^^^^^^^^^^^^^^ UP007 -31 | ... - | - = help: Convert to `X | Y` - -ℹ Possible fix -27 27 | ... -28 28 | -29 29 | -30 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: - 30 |+def f(x: "Union[str, int, float | bytes]") -> None: -31 31 | ... -32 32 | -33 33 | - -UP007.py:34:11: UP007 [*] Use `X | Y` for type annotations - | -34 | def f(x: "typing.Union[str, int]") -> None: - | ^^^^^^^^^^^^^^^^^^^^^^ UP007 +34 | def f(x: typing.Union[()]) -> None: + | ^^^^^^^^^^^^^^^^ UP007 35 | ... | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 31 31 | ... 32 32 | 33 33 | -34 |-def f(x: "typing.Union[str, int]") -> None: - 34 |+def f(x: "str | int") -> None: +34 |-def f(x: typing.Union[()]) -> None: + 34 |+def f(x: ()) -> None: 35 35 | ... 36 36 | 37 37 | -UP007.py:47:8: UP007 [*] Use `X | Y` for type annotations +UP007.py:38:11: UP007 [*] Use `X | Y` for type annotations | -46 | def f() -> None: -47 | x: Optional[str] - | ^^^^^^^^^^^^^ UP007 -48 | x = Optional[str] +38 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +39 | ... | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix +35 35 | ... +36 36 | +37 37 | +38 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: + 38 |+def f(x: "str | int | Union[float, bytes]") -> None: +39 39 | ... +40 40 | +41 41 | + +UP007.py:38:27: UP007 [*] Use `X | Y` for type annotations + | +38 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: + | ^^^^^^^^^^^^^^^^^^^ UP007 +39 | ... + | + = help: Convert to `X | Y` + +ℹ Suggested fix +35 35 | ... +36 36 | +37 37 | +38 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: + 38 |+def f(x: "Union[str, int, float | bytes]") -> None: +39 39 | ... +40 40 | +41 41 | + +UP007.py:42:11: UP007 [*] Use `X | Y` for type annotations + | +42 | def f(x: "typing.Union[str, int]") -> None: + | ^^^^^^^^^^^^^^^^^^^^^^ UP007 +43 | ... + | + = help: Convert to `X | Y` + +ℹ Suggested fix +39 39 | ... +40 40 | +41 41 | +42 |-def f(x: "typing.Union[str, int]") -> None: + 42 |+def f(x: "str | int") -> None: +43 43 | ... 44 44 | 45 45 | -46 46 | def f() -> None: -47 |- x: Optional[str] - 47 |+ x: str | None -48 48 | x = Optional[str] -49 49 | -50 50 | x = Union[str, int] -UP007.py:48:9: UP007 Use `X | Y` for type annotations +UP007.py:55:8: UP007 [*] Use `X | Y` for type annotations | -46 | def f() -> None: -47 | x: Optional[str] -48 | x = Optional[str] +54 | def f() -> None: +55 | x: Optional[str] + | ^^^^^^^^^^^^^ UP007 +56 | x = Optional[str] + | + = help: Convert to `X | Y` + +ℹ Suggested fix +52 52 | +53 53 | +54 54 | def f() -> None: +55 |- x: Optional[str] + 55 |+ x: str | None +56 56 | x = Optional[str] +57 57 | +58 58 | x = Union[str, int] + +UP007.py:56:9: UP007 Use `X | Y` for type annotations + | +54 | def f() -> None: +55 | x: Optional[str] +56 | x = Optional[str] | ^^^^^^^^^^^^^ UP007 -49 | -50 | x = Union[str, int] +57 | +58 | x = Union[str, int] | = help: Convert to `X | Y` -UP007.py:50:9: UP007 Use `X | Y` for type annotations +UP007.py:58:9: UP007 Use `X | Y` for type annotations | -48 | x = Optional[str] -49 | -50 | x = Union[str, int] +56 | x = Optional[str] +57 | +58 | x = Union[str, int] | ^^^^^^^^^^^^^^^ UP007 -51 | x = Union["str", "int"] -52 | x: Union[str, int] +59 | x = Union["str", "int"] +60 | x: Union[str, int] | = help: Convert to `X | Y` -UP007.py:52:8: UP007 [*] Use `X | Y` for type annotations +UP007.py:60:8: UP007 [*] Use `X | Y` for type annotations | -50 | x = Union[str, int] -51 | x = Union["str", "int"] -52 | x: Union[str, int] +58 | x = Union[str, int] +59 | x = Union["str", "int"] +60 | x: Union[str, int] | ^^^^^^^^^^^^^^^ UP007 -53 | x: Union["str", "int"] +61 | x: Union["str", "int"] | = help: Convert to `X | Y` -ℹ Possible fix -49 49 | -50 50 | x = Union[str, int] -51 51 | x = Union["str", "int"] -52 |- x: Union[str, int] - 52 |+ x: str | int -53 53 | x: Union["str", "int"] +ℹ Suggested fix +57 57 | +58 58 | x = Union[str, int] +59 59 | x = Union["str", "int"] +60 |- x: Union[str, int] + 60 |+ x: str | int +61 61 | x: Union["str", "int"] diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap index b20911af4e..a5a07f6b84 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap @@ -10,7 +10,7 @@ future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 37 37 | return y 38 38 | 39 39 | diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap index b025a5bc9b..db003ea51f 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap @@ -10,7 +10,7 @@ future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 37 37 | return y 38 38 | 39 39 | @@ -28,7 +28,7 @@ future_annotations.py:42:21: UP007 [*] Use `X | Y` for type annotations | = help: Convert to `X | Y` -ℹ Possible fix +ℹ Suggested fix 39 39 | 40 40 | x: Optional[int] = None 41 41 |