From 55bf85045c0e7bb96df3a7d0b1207da50794a537 Mon Sep 17 00:00:00 2001 From: Ahmed Ilyas Date: Sun, 12 May 2024 11:14:50 +0200 Subject: [PATCH 1/2] Split UP007 from Union and Optional to two individual rules --- .../test/fixtures/pyupgrade/UP007.py | 37 -- .../test/fixtures/pyupgrade/UP007B.py | 41 ++ .../resources/test/fixtures/ruff/redirects.py | 4 +- .../src/checkers/ast/analyze/expression.rs | 20 +- crates/ruff_linter/src/codes.rs | 3 +- crates/ruff_linter/src/rules/pyupgrade/mod.rs | 13 +- .../pyupgrade/rules/use_pep604_annotation.rs | 77 ++- ...er__rules__pyupgrade__tests__UP007.py.snap | 480 +++++++----------- ...r__rules__pyupgrade__tests__UP007B.py.snap | 133 +++++ ...tests__future_annotations_pep_604_p37.snap | 6 +- ...sts__future_annotations_pep_604_py310.snap | 6 +- crates/ruff_linter/src/rules/ruff/mod.rs | 5 +- ...linter__rules__ruff__tests__redirects.snap | 32 ++ ruff.schema.json | 1 + 14 files changed, 488 insertions(+), 370 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007.py index af1c1ca35b3a41..f933a4a995f56b 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007.py @@ -1,16 +1,7 @@ import typing -from typing import Optional from typing import Union -def f(x: Optional[str]) -> None: - ... - - -def f(x: typing.Optional[str]) -> None: - ... - - def f(x: Union[str, int, Union[float, bytes]]) -> None: ... @@ -52,9 +43,6 @@ def f(x: Union[("str", "int"), float]) -> None: def f() -> None: - x: Optional[str] - x = Optional[str] - x = Union[str, int] x = Union["str", "int"] x: Union[str, int] @@ -85,31 +73,6 @@ def f(x: Union[str, lambda: int]) -> None: ... -def f(x: Optional[int : float]) -> None: - ... - - -def f(x: Optional[str, int : float]) -> None: - ... - - -def f(x: Optional[int, float]) -> None: - ... - - -# Regression test for: https://github.com/astral-sh/ruff/issues/7131 -class ServiceRefOrValue: - service_specification: Optional[ - list[ServiceSpecificationRef] - | list[ServiceSpecification] - ] = None - - -# Regression test for: https://github.com/astral-sh/ruff/issues/7201 -class ServiceRefOrValue: - service_specification: Optional[str]is not True = None - - # Regression test for: https://github.com/astral-sh/ruff/issues/7452 class Collection(Protocol[*_B0]): def __iter__(self) -> Iterator[Union[*_B0]]: diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py new file mode 100644 index 00000000000000..633aea1af026f5 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py @@ -0,0 +1,41 @@ +import typing +from typing import Optional + + +def f(x: Optional[str]) -> None: + ... + + +def f(x: typing.Optional[str]) -> None: + ... + + +def f() -> None: + x: Optional[str] + x = Optional[str] + + +def f(x: Optional[int : float]) -> None: + ... + + +def f(x: Optional[str, int : float]) -> None: + ... + + +def f(x: Optional[int, float]) -> None: + ... + + +# Regression test for: https://github.com/astral-sh/ruff/issues/7131 +class ServiceRefOrValue: + service_specification: Optional[ + list[ServiceSpecificationRef] + | list[ServiceSpecification] + ] = None + + +# Regression test for: https://github.com/astral-sh/ruff/issues/7201 +class ServiceRefOrValue: + service_specification: Optional[str]is not True = None + diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/redirects.py b/crates/ruff_linter/resources/test/fixtures/ruff/redirects.py index 1464dd6c84aab5..6eafb5caa64ce5 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/redirects.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/redirects.py @@ -1,9 +1,9 @@ from typing import Optional -def f(x: Optional[str]) -> None: # noqa: U007 +def f(x: Optional[str]) -> None: # noqa: U007B ... -def f(x: Optional[str]) -> None: # noqa: UP007 +def f(x: Optional[str]) -> None: # noqa: UP007B ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index b526dd37777da6..9d3ab1ca45a520 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -27,7 +27,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { // Ex) Optional[...], Union[...] if checker.any_enabled(&[ Rule::FutureRewritableTypeAnnotation, - Rule::NonPEP604Annotation, + Rule::NonPEP604AnnotationOptional, + Rule::NonPEP604AnnotationUnion, ]) { if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic) { @@ -43,7 +44,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { ); } } - if checker.enabled(Rule::NonPEP604Annotation) { + if checker.enabled(Rule::NonPEP604AnnotationOptional) + && matches!(operator, typing::Pep604Operator::Optional) + { + if checker.source_type.is_stub() + || checker.settings.target_version >= PythonVersion::Py310 + || (checker.settings.target_version >= PythonVersion::Py37 + && checker.semantic.future_annotations_or_stub() + && checker.semantic.in_annotation() + && !checker.settings.pyupgrade.keep_runtime_typing) + { + pyupgrade::rules::use_pep604_annotation(checker, expr, slice, operator); + } + } + if checker.enabled(Rule::NonPEP604AnnotationUnion) + && matches!(operator, typing::Pep604Operator::Union) + { if checker.source_type.is_stub() || checker.settings.target_version >= PythonVersion::Py310 || (checker.settings.target_version >= PythonVersion::Py37 diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 05906db3c906b0..9cc4548e7af6e8 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -521,7 +521,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pyupgrade, "004") => (RuleGroup::Stable, rules::pyupgrade::rules::UselessObjectInheritance), (Pyupgrade, "005") => (RuleGroup::Stable, rules::pyupgrade::rules::DeprecatedUnittestAlias), (Pyupgrade, "006") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP585Annotation), - (Pyupgrade, "007") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP604Annotation), + (Pyupgrade, "007B") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationOptional), + (Pyupgrade, "007") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP604AnnotationUnion), (Pyupgrade, "008") => (RuleGroup::Stable, rules::pyupgrade::rules::SuperCallWithParameters), (Pyupgrade, "009") => (RuleGroup::Stable, rules::pyupgrade::rules::UTF8EncodingDeclaration), (Pyupgrade, "010") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryFutureImport), diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 427b448d765b1b..18955dbb891aca 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -39,7 +39,8 @@ mod tests { #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_1.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_2.py"))] #[test_case(Rule::NonPEP585Annotation, Path::new("UP006_3.py"))] - #[test_case(Rule::NonPEP604Annotation, Path::new("UP007.py"))] + #[test_case(Rule::NonPEP604AnnotationUnion, Path::new("UP007.py"))] + #[test_case(Rule::NonPEP604AnnotationOptional, Path::new("UP007B.py"))] #[test_case(Rule::NonPEP604Isinstance, Path::new("UP038.py"))] #[test_case(Rule::OSErrorAlias, Path::new("UP024_0.py"))] #[test_case(Rule::OSErrorAlias, Path::new("UP024_1.py"))] @@ -187,7 +188,10 @@ mod tests { Path::new("pyupgrade/future_annotations.py"), &settings::LinterSettings { target_version: PythonVersion::Py37, - ..settings::LinterSettings::for_rule(Rule::NonPEP604Annotation) + ..settings::LinterSettings::for_rules(vec![ + Rule::NonPEP604AnnotationUnion, + Rule::NonPEP604AnnotationOptional, + ]) }, )?; assert_messages!(diagnostics); @@ -200,7 +204,10 @@ mod tests { Path::new("pyupgrade/future_annotations.py"), &settings::LinterSettings { target_version: PythonVersion::Py310, - ..settings::LinterSettings::for_rule(Rule::NonPEP604Annotation) + ..settings::LinterSettings::for_rules(vec![ + Rule::NonPEP604AnnotationUnion, + Rule::NonPEP604AnnotationOptional, + ]) }, )?; assert_messages!(diagnostics); diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs index a33f2bbc231f3f..d53fae93866f14 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs @@ -10,12 +10,58 @@ use crate::fix::edits::pad; use crate::settings::types::PythonVersion; /// ## What it does -/// Check for type annotations that can be rewritten based on [PEP 604] syntax. +/// Check for `typing.Optional` annotations that can be rewritten based on [PEP 604] syntax. /// /// ## Why is this bad? /// [PEP 604] introduced a new syntax for union type annotations based on the /// `|` operator. This syntax is more concise and readable than the previous -/// `typing.Union` and `typing.Optional` syntaxes. +/// `typing.Optional` syntax. +/// +/// This rule is enabled when targeting Python 3.10 or later (see: +/// [`target-version`]). By default, it's _also_ enabled for earlier Python +/// versions if `from __future__ import annotations` is present, as +/// `__future__` annotations are not evaluated at runtime. If your code relies +/// on runtime type annotations (either directly or via a library like +/// Pydantic), you can disable this behavior for Python versions prior to 3.10 +/// by setting [`lint.pyupgrade.keep-runtime-typing`] to `true`. +/// +/// ## Example +/// ```python +/// from typing import Optional +/// +/// foo: Optional[int] = None +/// ``` +/// +/// Use instead: +/// ```python +/// foo: int | None = None +/// ``` +/// +/// ## Note +/// Previously, this rule was covered under the `UP007` rule, but it has now been moved to this new, specific rule. +/// +/// ## Fix safety +/// This rule's fix is marked as unsafe, as it may lead to runtime errors when +/// alongside libraries that rely on runtime type annotations, like Pydantic, +/// on Python versions prior to Python 3.10. It may also lead to runtime errors +/// in unusual and likely incorrect type annotations where the type does not +/// support the `|` operator. +/// +/// ## Options +/// - `target-version` +/// - `lint.pyupgrade.keep-runtime-typing` +/// +/// [PEP 604]: https://peps.python.org/pep-0604/ +#[violation] +pub struct NonPEP604AnnotationOptional; + +/// ## What it does +/// Check for `typing.Union` annotations that can be rewritten based on [PEP 604] syntax. +/// +/// ## Why is this bad? +/// [PEP 604] introduced a new syntax for union type annotations based on the +/// `|` operator. This syntax is more concise and readable than the previous +/// `typing.Union` syntax. /// /// This rule is enabled when targeting Python 3.10 or later (see: /// [`target-version`]). By default, it's _also_ enabled for earlier Python @@ -37,6 +83,10 @@ use crate::settings::types::PythonVersion; /// foo: int | str = 1 /// ``` /// +/// ## Note +/// Previously, this rule also covered the usage of `Optional[T]` => `T | None`. +/// This specific aspect of handling Optional types is now addressed by the `UP007B` rule instead. +/// /// ## Fix safety /// This rule's fix is marked as unsafe, as it may lead to runtime errors when /// alongside libraries that rely on runtime type annotations, like Pydantic, @@ -50,9 +100,22 @@ use crate::settings::types::PythonVersion; /// /// [PEP 604]: https://peps.python.org/pep-0604/ #[violation] -pub struct NonPEP604Annotation; +pub struct NonPEP604AnnotationUnion; + +impl Violation for NonPEP604AnnotationUnion { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + format!("Use `X | Y` for type annotations") + } + + fn fix_title(&self) -> Option { + Some("Convert to `X | Y`".to_string()) + } +} -impl Violation for NonPEP604Annotation { +impl Violation for NonPEP604AnnotationOptional { const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] @@ -65,7 +128,7 @@ impl Violation for NonPEP604Annotation { } } -/// UP007 +/// UP007 & UP007B pub(crate) fn use_pep604_annotation( checker: &mut Checker, expr: &Expr, @@ -86,7 +149,7 @@ pub(crate) fn use_pep604_annotation( match operator { Pep604Operator::Optional => { - let mut diagnostic = Diagnostic::new(NonPEP604Annotation, expr.range()); + let mut diagnostic = Diagnostic::new(NonPEP604AnnotationOptional, expr.range()); if fixable { match slice { Expr::Tuple(_) => { @@ -110,7 +173,7 @@ pub(crate) fn use_pep604_annotation( checker.diagnostics.push(diagnostic); } Pep604Operator::Union => { - let mut diagnostic = Diagnostic::new(NonPEP604Annotation, expr.range()); + let mut diagnostic = Diagnostic::new(NonPEP604AnnotationUnion, expr.range()); if fixable { match slice { Expr::Slice(_) => { diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007.py.snap index a607401c39adfb..dd4f4e2a92cda6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007.py.snap @@ -1,434 +1,296 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -UP007.py:6:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:5:10: UP007 [*] Use `X | Y` for type annotations | -6 | def f(x: Optional[str]) -> None: - | ^^^^^^^^^^^^^ UP007 -7 | ... +5 | def f(x: Union[str, int, Union[float, bytes]]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +6 | ... | = help: Convert to `X | Y` ℹ Safe fix -3 3 | from typing import Union +2 2 | from typing import Union +3 3 | 4 4 | -5 5 | -6 |-def f(x: Optional[str]) -> None: - 6 |+def f(x: str | None) -> None: -7 7 | ... +5 |-def f(x: Union[str, int, Union[float, bytes]]) -> None: + 5 |+def f(x: str | int | Union[float, bytes]) -> None: +6 6 | ... +7 7 | 8 8 | -9 9 | -UP007.py:10:10: UP007 [*] Use `X | Y` for type annotations - | -10 | def f(x: typing.Optional[str]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007 -11 | ... - | - = help: Convert to `X | Y` +UP007.py:5:26: UP007 [*] Use `X | Y` for type annotations + | +5 | def f(x: Union[str, int, Union[float, bytes]]) -> None: + | ^^^^^^^^^^^^^^^^^^^ UP007 +6 | ... + | + = help: Convert to `X | Y` ℹ Safe fix -7 7 | ... -8 8 | -9 9 | -10 |-def f(x: typing.Optional[str]) -> None: - 10 |+def f(x: str | None) -> None: -11 11 | ... -12 12 | -13 13 | +2 2 | from typing import Union +3 3 | +4 4 | +5 |-def f(x: Union[str, int, Union[float, bytes]]) -> None: + 5 |+def f(x: Union[str, int, float | bytes]) -> None: +6 6 | ... +7 7 | +8 8 | -UP007.py:14:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:9:10: UP007 [*] Use `X | Y` for type annotations | -14 | def f(x: Union[str, int, Union[float, bytes]]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 -15 | ... + 9 | def f(x: typing.Union[str, int]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^ UP007 +10 | ... | = help: Convert to `X | Y` ℹ Safe fix -11 11 | ... +6 6 | ... +7 7 | +8 8 | +9 |-def f(x: typing.Union[str, int]) -> None: + 9 |+def f(x: str | int) -> None: +10 10 | ... +11 11 | 12 12 | -13 13 | -14 |-def f(x: Union[str, int, Union[float, bytes]]) -> None: - 14 |+def f(x: str | int | Union[float, bytes]) -> None: -15 15 | ... -16 16 | -17 17 | -UP007.py:14:26: UP007 [*] Use `X | Y` for type annotations +UP007.py:13:10: UP007 [*] Use `X | Y` for type annotations | -14 | def f(x: Union[str, int, Union[float, bytes]]) -> None: - | ^^^^^^^^^^^^^^^^^^^ UP007 -15 | ... +13 | def f(x: typing.Union[(str, int)]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +14 | ... | = help: Convert to `X | Y` ℹ Safe fix -11 11 | ... +10 10 | ... +11 11 | 12 12 | -13 13 | -14 |-def f(x: Union[str, int, Union[float, bytes]]) -> None: - 14 |+def f(x: Union[str, int, float | bytes]) -> None: -15 15 | ... +13 |-def f(x: typing.Union[(str, int)]) -> None: + 13 |+def f(x: str | int) -> None: +14 14 | ... +15 15 | 16 16 | -17 17 | -UP007.py:18:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:17:10: UP007 [*] Use `X | Y` for type annotations | -18 | def f(x: typing.Union[str, int]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^ UP007 -19 | ... +17 | def f(x: typing.Union[(str, int), float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +18 | ... | = help: Convert to `X | Y` ℹ Safe fix -15 15 | ... +14 14 | ... +15 15 | 16 16 | -17 17 | -18 |-def f(x: typing.Union[str, int]) -> None: - 18 |+def f(x: str | int) -> None: -19 19 | ... +17 |-def f(x: typing.Union[(str, int), float]) -> None: + 17 |+def f(x: str | int | float) -> None: +18 18 | ... +19 19 | 20 20 | -21 21 | -UP007.py:22:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:21:10: UP007 [*] Use `X | Y` for type annotations | -22 | def f(x: typing.Union[(str, int)]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^ UP007 -23 | ... +21 | def f(x: typing.Union[(int,)]) -> None: + | ^^^^^^^^^^^^^^^^^^^^ UP007 +22 | ... | = help: Convert to `X | Y` ℹ Safe fix -19 19 | ... +18 18 | ... +19 19 | 20 20 | -21 21 | -22 |-def f(x: typing.Union[(str, int)]) -> None: - 22 |+def f(x: str | int) -> None: -23 23 | ... +21 |-def f(x: typing.Union[(int,)]) -> None: + 21 |+def f(x: int) -> None: +22 22 | ... +23 23 | 24 24 | -25 25 | -UP007.py:26:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:25:10: UP007 [*] Use `X | Y` for type annotations | -26 | def f(x: typing.Union[(str, int), float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 -27 | ... +25 | def f(x: typing.Union[()]) -> None: + | ^^^^^^^^^^^^^^^^ UP007 +26 | ... | = help: Convert to `X | Y` ℹ Safe fix -23 23 | ... +22 22 | ... +23 23 | 24 24 | -25 25 | -26 |-def f(x: typing.Union[(str, int), float]) -> None: - 26 |+def f(x: str | int | float) -> None: -27 27 | ... +25 |-def f(x: typing.Union[()]) -> None: + 25 |+def f(x: ()) -> None: +26 26 | ... +27 27 | 28 28 | -29 29 | -UP007.py:30:10: UP007 [*] Use `X | Y` for type annotations +UP007.py:29:11: UP007 [*] Use `X | Y` for type annotations | -30 | def f(x: typing.Union[(int,)]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007 -31 | ... +29 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 +30 | ... | = help: Convert to `X | Y` ℹ Safe fix -27 27 | ... +26 26 | ... +27 27 | 28 28 | -29 29 | -30 |-def f(x: typing.Union[(int,)]) -> None: - 30 |+def f(x: int) -> None: -31 31 | ... -32 32 | -33 33 | - -UP007.py:34:10: UP007 [*] Use `X | Y` for type annotations - | -34 | def f(x: typing.Union[()]) -> None: - | ^^^^^^^^^^^^^^^^ UP007 -35 | ... - | - = help: Convert to `X | Y` - -ℹ Safe fix -31 31 | ... +29 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: + 29 |+def f(x: "str | int | Union[float, bytes]") -> None: +30 30 | ... +31 31 | 32 32 | -33 33 | -34 |-def f(x: typing.Union[()]) -> None: - 34 |+def f(x: ()) -> None: -35 35 | ... -36 36 | -37 37 | - -UP007.py:38:11: 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` -ℹ Safe 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 +UP007.py:29:27: UP007 [*] Use `X | Y` for type annotations | -38 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: +29 | def f(x: "Union[str, int, Union[float, bytes]]") -> None: | ^^^^^^^^^^^^^^^^^^^ UP007 -39 | ... +30 | ... | = help: Convert to `X | Y` ℹ Safe 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 | +26 26 | ... +27 27 | +28 28 | +29 |-def f(x: "Union[str, int, Union[float, bytes]]") -> None: + 29 |+def f(x: "Union[str, int, float | bytes]") -> None: +30 30 | ... +31 31 | +32 32 | -UP007.py:42:11: UP007 [*] Use `X | Y` for type annotations +UP007.py:33:11: UP007 [*] Use `X | Y` for type annotations | -42 | def f(x: "typing.Union[str, int]") -> None: +33 | def f(x: "typing.Union[str, int]") -> None: | ^^^^^^^^^^^^^^^^^^^^^^ UP007 -43 | ... - | - = help: Convert to `X | Y` - -ℹ Safe 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 | - -UP007.py:55:8: UP007 [*] Use `X | Y` for type annotations - | -54 | def f() -> None: -55 | x: Optional[str] - | ^^^^^^^^^^^^^ UP007 -56 | x = Optional[str] +34 | ... | = help: Convert to `X | Y` ℹ Safe 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 -57 | -58 | x = Union[str, int] - | - = help: Convert to `X | Y` +30 30 | ... +31 31 | +32 32 | +33 |-def f(x: "typing.Union[str, int]") -> None: + 33 |+def f(x: "str | int") -> None: +34 34 | ... +35 35 | +36 36 | -UP007.py:58:9: UP007 Use `X | Y` for type annotations +UP007.py:46:9: UP007 Use `X | Y` for type annotations | -56 | x = Optional[str] -57 | -58 | x = Union[str, int] +45 | def f() -> None: +46 | x = Union[str, int] | ^^^^^^^^^^^^^^^ UP007 -59 | x = Union["str", "int"] -60 | x: Union[str, int] +47 | x = Union["str", "int"] +48 | x: Union[str, int] | = help: Convert to `X | Y` -UP007.py:60:8: UP007 [*] Use `X | Y` for type annotations +UP007.py:48:8: UP007 [*] Use `X | Y` for type annotations | -58 | x = Union[str, int] -59 | x = Union["str", "int"] -60 | x: Union[str, int] +46 | x = Union[str, int] +47 | x = Union["str", "int"] +48 | x: Union[str, int] | ^^^^^^^^^^^^^^^ UP007 -61 | x: Union["str", "int"] +49 | x: Union["str", "int"] | = help: Convert to `X | Y` ℹ Safe 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"] -62 62 | -63 63 | - -UP007.py:61:8: UP007 [*] Use `X | Y` for type annotations - | -59 | x = Union["str", "int"] -60 | x: Union[str, int] -61 | x: Union["str", "int"] +45 45 | def f() -> None: +46 46 | x = Union[str, int] +47 47 | x = Union["str", "int"] +48 |- x: Union[str, int] + 48 |+ x: str | int +49 49 | x: Union["str", "int"] +50 50 | +51 51 | + +UP007.py:49:8: UP007 [*] Use `X | Y` for type annotations + | +47 | x = Union["str", "int"] +48 | x: Union[str, int] +49 | x: Union["str", "int"] | ^^^^^^^^^^^^^^^^^^^ UP007 | = help: Convert to `X | Y` ℹ Safe fix -58 58 | x = Union[str, int] -59 59 | x = Union["str", "int"] -60 60 | x: Union[str, int] -61 |- x: Union["str", "int"] - 61 |+ x: "str" | "int" -62 62 | -63 63 | -64 64 | def f(x: Union[int : float]) -> None: - -UP007.py:64:10: UP007 Use `X | Y` for type annotations - | -64 | def f(x: Union[int : float]) -> None: +46 46 | x = Union[str, int] +47 47 | x = Union["str", "int"] +48 48 | x: Union[str, int] +49 |- x: Union["str", "int"] + 49 |+ x: "str" | "int" +50 50 | +51 51 | +52 52 | def f(x: Union[int : float]) -> None: + +UP007.py:52:10: UP007 Use `X | Y` for type annotations + | +52 | def f(x: Union[int : float]) -> None: | ^^^^^^^^^^^^^^^^^^ UP007 -65 | ... +53 | ... | = help: Convert to `X | Y` -UP007.py:68:10: UP007 Use `X | Y` for type annotations +UP007.py:56:10: UP007 Use `X | Y` for type annotations | -68 | def f(x: Union[str, int : float]) -> None: +56 | def f(x: Union[str, int : float]) -> None: | ^^^^^^^^^^^^^^^^^^^^^^^ UP007 -69 | ... +57 | ... | = help: Convert to `X | Y` -UP007.py:72:10: UP007 Use `X | Y` for type annotations +UP007.py:60:10: UP007 Use `X | Y` for type annotations | -72 | def f(x: Union[x := int]) -> None: +60 | def f(x: Union[x := int]) -> None: | ^^^^^^^^^^^^^^^ UP007 -73 | ... +61 | ... | = help: Convert to `X | Y` -UP007.py:76:10: UP007 Use `X | Y` for type annotations +UP007.py:64:10: UP007 Use `X | Y` for type annotations | -76 | def f(x: Union[str, x := int]) -> None: +64 | def f(x: Union[str, x := int]) -> None: | ^^^^^^^^^^^^^^^^^^^^ UP007 -77 | ... +65 | ... | = help: Convert to `X | Y` -UP007.py:80:10: UP007 Use `X | Y` for type annotations +UP007.py:68:10: UP007 Use `X | Y` for type annotations | -80 | def f(x: Union[lambda: int]) -> None: +68 | def f(x: Union[lambda: int]) -> None: | ^^^^^^^^^^^^^^^^^^ UP007 -81 | ... +69 | ... | = help: Convert to `X | Y` -UP007.py:84:10: UP007 Use `X | Y` for type annotations +UP007.py:72:10: UP007 Use `X | Y` for type annotations | -84 | def f(x: Union[str, lambda: int]) -> None: +72 | def f(x: Union[str, lambda: int]) -> None: | ^^^^^^^^^^^^^^^^^^^^^^^ UP007 -85 | ... - | - = help: Convert to `X | Y` - -UP007.py:88:10: UP007 Use `X | Y` for type annotations - | -88 | def f(x: Optional[int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^ UP007 -89 | ... - | - = help: Convert to `X | Y` - -UP007.py:92:10: UP007 Use `X | Y` for type annotations - | -92 | def f(x: Optional[str, int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007 -93 | ... +73 | ... | = help: Convert to `X | Y` -UP007.py:96:10: UP007 Use `X | Y` for type annotations +UP007.py:83:10: UP007 [*] Use `X | Y` for type annotations | -96 | def f(x: Optional[int, float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007 -97 | ... +82 | # Regression test for: https://github.com/astral-sh/ruff/issues/8609 +83 | def f(x: Union[int, str, bytes]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^ UP007 +84 | ... | = help: Convert to `X | Y` -UP007.py:102:28: UP007 [*] Use `X | Y` for type annotations - | -100 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 -101 | class ServiceRefOrValue: -102 | service_specification: Optional[ - | ____________________________^ -103 | | list[ServiceSpecificationRef] -104 | | | list[ServiceSpecification] -105 | | ] = None - | |_____^ UP007 - | - = help: Convert to `X | Y` - -ℹ Safe fix -99 99 | -100 100 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 -101 101 | class ServiceRefOrValue: -102 |- service_specification: Optional[ -103 |- list[ServiceSpecificationRef] -104 |- | list[ServiceSpecification] -105 |- ] = None - 102 |+ service_specification: list[ServiceSpecificationRef] | list[ServiceSpecification] | None = None -106 103 | -107 104 | -108 105 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 - -UP007.py:110:28: UP007 [*] Use `X | Y` for type annotations - | -108 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -109 | class ServiceRefOrValue: -110 | service_specification: Optional[str]is not True = None - | ^^^^^^^^^^^^^ UP007 - | - = help: Convert to `X | Y` - -ℹ Safe fix -107 107 | -108 108 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -109 109 | class ServiceRefOrValue: -110 |- service_specification: Optional[str]is not True = None - 110 |+ service_specification: str | None is not True = None -111 111 | -112 112 | -113 113 | # Regression test for: https://github.com/astral-sh/ruff/issues/7452 - -UP007.py:120:10: UP007 [*] Use `X | Y` for type annotations - | -119 | # Regression test for: https://github.com/astral-sh/ruff/issues/8609 -120 | def f(x: Union[int, str, bytes]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^ UP007 -121 | ... - | - = help: Convert to `X | Y` - ℹ Safe fix -117 117 | -118 118 | -119 119 | # Regression test for: https://github.com/astral-sh/ruff/issues/8609 -120 |-def f(x: Union[int, str, bytes]) -> None: - 120 |+def f(x: int | str | bytes) -> None: -121 121 | ... - - +80 80 | +81 81 | +82 82 | # Regression test for: https://github.com/astral-sh/ruff/issues/8609 +83 |-def f(x: Union[int, str, bytes]) -> None: + 83 |+def f(x: int | str | bytes) -> None: +84 84 | ... diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap new file mode 100644 index 00000000000000..bc5bd486fca598 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap @@ -0,0 +1,133 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP007B.py:5:10: UP007B [*] Use `X | Y` for type annotations + | +5 | def f(x: Optional[str]) -> None: + | ^^^^^^^^^^^^^ UP007B +6 | ... + | + = help: Convert to `X | Y` + +ℹ Safe fix +2 2 | from typing import Optional +3 3 | +4 4 | +5 |-def f(x: Optional[str]) -> None: + 5 |+def f(x: str | None) -> None: +6 6 | ... +7 7 | +8 8 | + +UP007B.py:9:10: UP007B [*] Use `X | Y` for type annotations + | + 9 | def f(x: typing.Optional[str]) -> None: + | ^^^^^^^^^^^^^^^^^^^^ UP007B +10 | ... + | + = help: Convert to `X | Y` + +ℹ Safe fix +6 6 | ... +7 7 | +8 8 | +9 |-def f(x: typing.Optional[str]) -> None: + 9 |+def f(x: str | None) -> None: +10 10 | ... +11 11 | +12 12 | + +UP007B.py:14:8: UP007B [*] Use `X | Y` for type annotations + | +13 | def f() -> None: +14 | x: Optional[str] + | ^^^^^^^^^^^^^ UP007B +15 | x = Optional[str] + | + = help: Convert to `X | Y` + +ℹ Safe fix +11 11 | +12 12 | +13 13 | def f() -> None: +14 |- x: Optional[str] + 14 |+ x: str | None +15 15 | x = Optional[str] +16 16 | +17 17 | + +UP007B.py:15:9: UP007B Use `X | Y` for type annotations + | +13 | def f() -> None: +14 | x: Optional[str] +15 | x = Optional[str] + | ^^^^^^^^^^^^^ UP007B + | + = help: Convert to `X | Y` + +UP007B.py:18:10: UP007B Use `X | Y` for type annotations + | +18 | def f(x: Optional[int : float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^ UP007B +19 | ... + | + = help: Convert to `X | Y` + +UP007B.py:22:10: UP007B Use `X | Y` for type annotations + | +22 | def f(x: Optional[str, int : float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007B +23 | ... + | + = help: Convert to `X | Y` + +UP007B.py:26:10: UP007B Use `X | Y` for type annotations + | +26 | def f(x: Optional[int, float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^ UP007B +27 | ... + | + = help: Convert to `X | Y` + +UP007B.py:32:28: UP007B [*] Use `X | Y` for type annotations + | +30 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 +31 | class ServiceRefOrValue: +32 | service_specification: Optional[ + | ____________________________^ +33 | | list[ServiceSpecificationRef] +34 | | | list[ServiceSpecification] +35 | | ] = None + | |_____^ UP007B + | + = help: Convert to `X | Y` + +ℹ Safe fix +29 29 | +30 30 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 +31 31 | class ServiceRefOrValue: +32 |- service_specification: Optional[ +33 |- list[ServiceSpecificationRef] +34 |- | list[ServiceSpecification] +35 |- ] = None + 32 |+ service_specification: list[ServiceSpecificationRef] | list[ServiceSpecification] | None = None +36 33 | +37 34 | +38 35 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 + +UP007B.py:40:28: UP007B [*] Use `X | Y` for type annotations + | +38 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 +39 | class ServiceRefOrValue: +40 | service_specification: Optional[str]is not True = None + | ^^^^^^^^^^^^^ UP007B + | + = help: Convert to `X | Y` + +ℹ Safe fix +37 37 | +38 38 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 +39 39 | class ServiceRefOrValue: +40 |- service_specification: Optional[str]is not True = None + 40 |+ service_specification: str | None is not True = None +41 41 | diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap index 442d20f7a54760..8d8ba7d4691983 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_p37.snap @@ -1,10 +1,10 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations +future_annotations.py:40:4: UP007B [*] Use `X | Y` for type annotations | 40 | x: Optional[int] = None - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP007B 41 | 42 | MyList: TypeAlias = Union[List[int], List[str]] | @@ -18,5 +18,3 @@ future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations 40 |+x: int | None = None 41 41 | 42 42 | MyList: TypeAlias = Union[List[int], List[str]] - - diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap index 386e009dfa9cb3..3a961d96448197 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__future_annotations_pep_604_py310.snap @@ -1,10 +1,10 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -future_annotations.py:40:4: UP007 [*] Use `X | Y` for type annotations +future_annotations.py:40:4: UP007B [*] Use `X | Y` for type annotations | 40 | x: Optional[int] = None - | ^^^^^^^^^^^^^ UP007 + | ^^^^^^^^^^^^^ UP007B 41 | 42 | MyList: TypeAlias = Union[List[int], List[str]] | @@ -34,5 +34,3 @@ future_annotations.py:42:21: UP007 [*] Use `X | Y` for type annotations 41 41 | 42 |-MyList: TypeAlias = Union[List[int], List[str]] 42 |+MyList: TypeAlias = List[int] | List[str] - - diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 7bce548d85dd1a..f4a46d10ac7222 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -319,7 +319,10 @@ mod tests { fn redirects() -> Result<()> { let diagnostics = test_path( Path::new("ruff/redirects.py"), - &settings::LinterSettings::for_rules(vec![Rule::NonPEP604Annotation]), + &settings::LinterSettings::for_rules(vec![ + Rule::NonPEP604AnnotationOptional, + Rule::NonPEP604AnnotationUnion, + ]), )?; assert_messages!(diagnostics); Ok(()) diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__redirects.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__redirects.snap index 7f58cfd7246a31..8cfbeffc81e32f 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__redirects.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__redirects.snap @@ -1,4 +1,36 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- +redirects.py:4:10: UP007B [*] Use `X | Y` for type annotations + | +4 | def f(x: Optional[str]) -> None: # noqa: U007B + | ^^^^^^^^^^^^^ UP007B +5 | ... + | + = help: Convert to `X | Y` +ℹ Safe fix +1 1 | from typing import Optional +2 2 | +3 3 | +4 |-def f(x: Optional[str]) -> None: # noqa: U007B + 4 |+def f(x: str | None) -> None: # noqa: U007B +5 5 | ... +6 6 | +7 7 | + +redirects.py:8:10: UP007B [*] Use `X | Y` for type annotations + | +8 | def f(x: Optional[str]) -> None: # noqa: UP007B + | ^^^^^^^^^^^^^ UP007B +9 | ... + | + = help: Convert to `X | Y` + +ℹ Safe fix +5 5 | ... +6 6 | +7 7 | +8 |-def f(x: Optional[str]) -> None: # noqa: UP007B + 8 |+def f(x: str | None) -> None: # noqa: UP007B +9 9 | ... diff --git a/ruff.schema.json b/ruff.schema.json index 33cc16342c78f9..96cdde751c4a2a 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3851,6 +3851,7 @@ "UP005", "UP006", "UP007", + "UP007B", "UP008", "UP009", "UP01", From a66ded93f2fc7d384ef5188510de0c13fe2e6808 Mon Sep 17 00:00:00 2001 From: Ahmed Ilyas Date: Sun, 12 May 2024 17:34:16 +0200 Subject: [PATCH 2/2] add a test for nested Optional case --- .../test/fixtures/pyupgrade/UP007B.py | 4 + ...r__rules__pyupgrade__tests__UP007B.py.snap | 88 +++++++++++-------- 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py index 633aea1af026f5..91967f784ec15a 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007B.py @@ -15,6 +15,10 @@ def f() -> None: x = Optional[str] +def f(x: list[Optional[int]]) -> None: + ... + + def f(x: Optional[int : float]) -> None: ... diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap index bc5bd486fca598..bb845fa8040fe4 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007B.py.snap @@ -65,69 +65,87 @@ UP007B.py:15:9: UP007B Use `X | Y` for type annotations | = help: Convert to `X | Y` -UP007B.py:18:10: UP007B Use `X | Y` for type annotations +UP007B.py:18:15: UP007B [*] Use `X | Y` for type annotations | -18 | def f(x: Optional[int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^ UP007B +18 | def f(x: list[Optional[int]]) -> None: + | ^^^^^^^^^^^^^ UP007B 19 | ... | = help: Convert to `X | Y` +ℹ Safe fix +15 15 | x = Optional[str] +16 16 | +17 17 | +18 |-def f(x: list[Optional[int]]) -> None: + 18 |+def f(x: list[int | None]) -> None: +19 19 | ... +20 20 | +21 21 | + UP007B.py:22:10: UP007B Use `X | Y` for type annotations | -22 | def f(x: Optional[str, int : float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007B +22 | def f(x: Optional[int : float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^ UP007B 23 | ... | = help: Convert to `X | Y` UP007B.py:26:10: UP007B Use `X | Y` for type annotations | -26 | def f(x: Optional[int, float]) -> None: - | ^^^^^^^^^^^^^^^^^^^^ UP007B +26 | def f(x: Optional[str, int : float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP007B 27 | ... | = help: Convert to `X | Y` -UP007B.py:32:28: UP007B [*] Use `X | Y` for type annotations +UP007B.py:30:10: UP007B Use `X | Y` for type annotations + | +30 | def f(x: Optional[int, float]) -> None: + | ^^^^^^^^^^^^^^^^^^^^ UP007B +31 | ... + | + = help: Convert to `X | Y` + +UP007B.py:36:28: UP007B [*] Use `X | Y` for type annotations | -30 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 -31 | class ServiceRefOrValue: -32 | service_specification: Optional[ +34 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 +35 | class ServiceRefOrValue: +36 | service_specification: Optional[ | ____________________________^ -33 | | list[ServiceSpecificationRef] -34 | | | list[ServiceSpecification] -35 | | ] = None +37 | | list[ServiceSpecificationRef] +38 | | | list[ServiceSpecification] +39 | | ] = None | |_____^ UP007B | = help: Convert to `X | Y` ℹ Safe fix -29 29 | -30 30 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 -31 31 | class ServiceRefOrValue: -32 |- service_specification: Optional[ -33 |- list[ServiceSpecificationRef] -34 |- | list[ServiceSpecification] -35 |- ] = None - 32 |+ service_specification: list[ServiceSpecificationRef] | list[ServiceSpecification] | None = None -36 33 | -37 34 | -38 35 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 +33 33 | +34 34 | # Regression test for: https://github.com/astral-sh/ruff/issues/7131 +35 35 | class ServiceRefOrValue: +36 |- service_specification: Optional[ +37 |- list[ServiceSpecificationRef] +38 |- | list[ServiceSpecification] +39 |- ] = None + 36 |+ service_specification: list[ServiceSpecificationRef] | list[ServiceSpecification] | None = None +40 37 | +41 38 | +42 39 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -UP007B.py:40:28: UP007B [*] Use `X | Y` for type annotations +UP007B.py:44:28: UP007B [*] Use `X | Y` for type annotations | -38 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -39 | class ServiceRefOrValue: -40 | service_specification: Optional[str]is not True = None +42 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 +43 | class ServiceRefOrValue: +44 | service_specification: Optional[str]is not True = None | ^^^^^^^^^^^^^ UP007B | = help: Convert to `X | Y` ℹ Safe fix -37 37 | -38 38 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 -39 39 | class ServiceRefOrValue: -40 |- service_specification: Optional[str]is not True = None - 40 |+ service_specification: str | None is not True = None -41 41 | +41 41 | +42 42 | # Regression test for: https://github.com/astral-sh/ruff/issues/7201 +43 43 | class ServiceRefOrValue: +44 |- service_specification: Optional[str]is not True = None + 44 |+ service_specification: str | None is not True = None +45 45 |