From b7d716083908262e0e19ac175131b7261d6c912a Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Sun, 29 Jun 2025 12:35:26 +0200 Subject: [PATCH 1/4] [`flake8_pyi`] Fix `PYI041` not resolving string annotations --- .../test/fixtures/flake8_pyi/PYI041_3.py | 112 ++++++ crates/ruff_linter/src/checkers/ast/mod.rs | 20 + .../ruff_linter/src/rules/flake8_pyi/mod.rs | 1 + .../rules/redundant_numeric_union.rs | 14 +- ...flake8_pyi__tests__PYI041_PYI041_3.py.snap | 361 ++++++++++++++++++ 5 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py new file mode 100644 index 0000000000..79cd0f2ce6 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py @@ -0,0 +1,112 @@ +from typing import ( + TYPE_CHECKING, + Union, +) + +from typing_extensions import ( + TypeAlias, +) + +TA0: TypeAlias = "int" +TA1: TypeAlias = "int | float | bool" +TA2: TypeAlias = "Union[int, float, bool]" + + +def good1(arg: "int") -> "int | bool": + ... + + +def good2(arg: "int", arg2: "int | bool") -> "None": + ... + + +def f0(arg1: "float | int") -> "None": + ... + + +def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": + ... + + +def f2(arg1: "int", /, arg2: "int | int | float") -> "None": + ... + + +def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": + ... + + +async def f4(**kwargs: "int | int | float") -> "None": + ... + + +def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": + ... + + +def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": + ... + + +def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": + ... + + +def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": + ... + + +def f9( + arg: """Union[ # comment + float, # another + complex, int]""" + ) -> "None": + ... + +def f10( + arg: """ + int | # comment + float | # another + complex + """ + ) -> "None": + ... + + +class Foo: + def good(self, arg: "int") -> "None": + ... + + def bad(self, arg: "int | float | complex") -> "None": + ... + + def bad2(self, arg: "int | Union[float, complex]") -> "None": + ... + + def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": + ... + + def bad4(self, arg: "Union[float | complex, int]") -> "None": + ... + + def bad5(self, arg: "int | (float | complex)") -> "None": + ... + + +# https://github.com/astral-sh/ruff/issues/18298 +# fix must not yield runtime `None | None | ...` (TypeError) +class Issue18298: + def f1(self, arg: "None | int | None | float" = None) -> "None": # PYI041 - no fix + pass + + if TYPE_CHECKING: + + def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix + + else: + + def f2(self, arg=None) -> "None": + pass + + def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix + pass diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index f3a9c2a04a..cd998ac9d4 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -538,6 +538,26 @@ impl<'a> Checker<'a> { } } + /// Apply a mapper function to an annotation expression, + /// abstracting over the fact that the annotation expression might be "stringized". + /// + /// A stringized annotation is one enclosed in string quotes: + /// `foo: "typing.Any"` means the same thing to a type checker as `foo: typing.Any`. + pub(crate) fn map_maybe_stringized_annotation( + &self, + expr: &ast::Expr, + map_fn: impl FnOnce(&ast::Expr) -> T, + ) -> T { + if let ast::Expr::StringLiteral(string_annotation) = expr { + let Some(parsed_annotation) = self.parse_type_annotation(string_annotation).ok() else { + return map_fn(expr); + }; + map_fn(parsed_annotation.expression()) + } else { + map_fn(expr) + } + } + /// Push `diagnostic` if the checker is not in a `@no_type_check` context. pub(crate) fn report_type_diagnostic(&self, kind: T, range: TextRange) { if !self.semantic.in_no_type_check() { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index a065946483..1c0c0408ac 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -76,6 +76,7 @@ mod tests { #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_1.py"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_1.pyi"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_2.py"))] + #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_3.py"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index b493474929..27b37e2e36 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -80,11 +80,13 @@ impl Violation for RedundantNumericUnion { /// PYI041 pub(crate) fn redundant_numeric_union(checker: &Checker, parameters: &Parameters) { for annotation in parameters.iter().filter_map(AnyParameterRef::annotation) { - check_annotation(checker, annotation); + checker.map_maybe_stringized_annotation(annotation, |resolved_annotation| { + check_annotation(checker, resolved_annotation, annotation); + }); } } -fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) { +fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr, unresolved_annotation: &'a Expr) { let mut numeric_flags = NumericFlags::empty(); let mut find_numeric_type = |expr: &Expr, _parent: &Expr| { @@ -141,8 +143,14 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) { return; } + let string_annotation = unresolved_annotation + .as_string_literal_expr() + .map(|str| str.value.to_str()); + // Mark [`Fix`] as unsafe when comments are in range. - let applicability = if checker.comment_ranges().intersects(annotation.range()) { + let applicability = if string_annotation.is_some_and(|s| s.contains('#')) + || checker.comment_ranges().intersects(annotation.range()) + { Applicability::Unsafe } else { Applicability::Safe diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap new file mode 100644 index 0000000000..b88edb0a6e --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap @@ -0,0 +1,361 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI041_3.py:23:15: PYI041 [*] Use `float` instead of `int | float` + | +23 | def f0(arg1: "float | int") -> "None": + | ^^^^^^^^^^^ PYI041 +24 | ... + | + = help: Remove redundant type + +ℹ Safe fix +20 20 | ... +21 21 | +22 22 | +23 |-def f0(arg1: "float | int") -> "None": + 23 |+def f0(arg1: "float") -> "None": +24 24 | ... +25 25 | +26 26 | + +PYI041_3.py:27:33: PYI041 [*] Use `complex` instead of `float | complex` + | +27 | def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +28 | ... + | + = help: Remove redundant type + +ℹ Safe fix +24 24 | ... +25 25 | +26 26 | +27 |-def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": + 27 |+def f1(arg1: "float", *, arg2: "list[str] | type[bool] | complex") -> "None": +28 28 | ... +29 29 | +30 30 | + +PYI041_3.py:31:31: PYI041 [*] Use `float` instead of `int | float` + | +31 | def f2(arg1: "int", /, arg2: "int | int | float") -> "None": + | ^^^^^^^^^^^^^^^^^ PYI041 +32 | ... + | + = help: Remove redundant type + +ℹ Safe fix +28 28 | ... +29 29 | +30 30 | +31 |-def f2(arg1: "int", /, arg2: "int | int | float") -> "None": + 31 |+def f2(arg1: "int", /, arg2: "float") -> "None": +32 32 | ... +33 33 | +34 34 | + +PYI041_3.py:35:29: PYI041 [*] Use `float` instead of `int | float` + | +35 | def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +36 | ... + | + = help: Remove redundant type + +ℹ Safe fix +32 32 | ... +33 33 | +34 34 | +35 |-def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": + 35 |+def f3(arg1: "int", *args: "float") -> "None": +36 36 | ... +37 37 | +38 38 | + +PYI041_3.py:39:25: PYI041 [*] Use `float` instead of `int | float` + | +39 | async def f4(**kwargs: "int | int | float") -> "None": + | ^^^^^^^^^^^^^^^^^ PYI041 +40 | ... + | + = help: Remove redundant type + +ℹ Safe fix +36 36 | ... +37 37 | +38 38 | +39 |-async def f4(**kwargs: "int | int | float") -> "None": + 39 |+async def f4(**kwargs: "float") -> "None": +40 40 | ... +41 41 | +42 42 | + +PYI041_3.py:43:29: PYI041 [*] Use `float` instead of `int | float` + | +43 | def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^ PYI041 +44 | ... + | + = help: Remove redundant type + +ℹ Safe fix +40 40 | ... +41 41 | +42 42 | +43 |-def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": + 43 |+def f5(arg1: "int", *args: "float") -> "None": +44 44 | ... +45 45 | +46 46 | + +PYI041_3.py:47:29: PYI041 [*] Use `float` instead of `int | float` + | +47 | def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +48 | ... + | + = help: Remove redundant type + +ℹ Safe fix +44 44 | ... +45 45 | +46 46 | +47 |-def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": + 47 |+def f6(arg1: "int", *args: "float") -> "None": +48 48 | ... +49 49 | +50 50 | + +PYI041_3.py:51:29: PYI041 [*] Use `float` instead of `int | float` + | +51 | def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +52 | ... + | + = help: Remove redundant type + +ℹ Safe fix +48 48 | ... +49 49 | +50 50 | +51 |-def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": + 51 |+def f7(arg1: "int", *args: "float") -> "None": +52 52 | ... +53 53 | +54 54 | + +PYI041_3.py:55:29: PYI041 [*] Use `float` instead of `int | float` + | +55 | def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +56 | ... + | + = help: Remove redundant type + +ℹ Safe fix +52 52 | ... +53 53 | +54 54 | +55 |-def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": + 55 |+def f8(arg1: "int", *args: "float") -> "None": +56 56 | ... +57 57 | +58 58 | + +PYI041_3.py:60:13: PYI041 [*] Use `complex` instead of `int | float | complex` + | +59 | def f9( +60 | arg: """Union[ # comment + | _____________^ +61 | | float, # another +62 | | complex, int]""" + | |_____________________^ PYI041 +63 | ) -> "None": +64 | ... + | + = help: Remove redundant type + +ℹ Unsafe fix +57 57 | +58 58 | +59 59 | def f9( +60 |- arg: """Union[ # comment +61 |- float, # another +62 |- complex, int]""" + 60 |+ arg: """complex""" +63 61 | ) -> "None": +64 62 | ... +65 63 | + +PYI041_3.py:68:9: PYI041 [*] Use `complex` instead of `int | float | complex` + | +66 | def f10( +67 | arg: """ +68 | / int | # comment +69 | | float | # another +70 | | complex + | |_______________^ PYI041 +71 | """ +72 | ) -> "None": + | + = help: Remove redundant type + +ℹ Unsafe fix +65 65 | +66 66 | def f10( +67 67 | arg: """ +68 |- int | # comment +69 |- float | # another +70 68 | complex +71 69 | """ +72 70 | ) -> "None": + +PYI041_3.py:80:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +78 | ... +79 | +80 | def bad(self, arg: "int | float | complex") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^ PYI041 +81 | ... + | + = help: Remove redundant type + +ℹ Safe fix +77 77 | def good(self, arg: "int") -> "None": +78 78 | ... +79 79 | +80 |- def bad(self, arg: "int | float | complex") -> "None": + 80 |+ def bad(self, arg: "complex") -> "None": +81 81 | ... +82 82 | +83 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": + +PYI041_3.py:83:26: PYI041 [*] Use `complex` instead of `int | float | complex` + | +81 | ... +82 | +83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +84 | ... + | + = help: Remove redundant type + +ℹ Safe fix +80 80 | def bad(self, arg: "int | float | complex") -> "None": +81 81 | ... +82 82 | +83 |- def bad2(self, arg: "int | Union[float, complex]") -> "None": + 83 |+ def bad2(self, arg: "complex") -> "None": +84 84 | ... +85 85 | +86 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": + +PYI041_3.py:86:26: PYI041 [*] Use `complex` instead of `int | float | complex` + | +84 | ... +85 | +86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +87 | ... + | + = help: Remove redundant type + +ℹ Safe fix +83 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": +84 84 | ... +85 85 | +86 |- def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": + 86 |+ def bad3(self, arg: "complex") -> "None": +87 87 | ... +88 88 | +89 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": + +PYI041_3.py:89:26: PYI041 [*] Use `complex` instead of `int | float | complex` + | +87 | ... +88 | +89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +90 | ... + | + = help: Remove redundant type + +ℹ Safe fix +86 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": +87 87 | ... +88 88 | +89 |- def bad4(self, arg: "Union[float | complex, int]") -> "None": + 89 |+ def bad4(self, arg: "complex") -> "None": +90 90 | ... +91 91 | +92 92 | def bad5(self, arg: "int | (float | complex)") -> "None": + +PYI041_3.py:92:26: PYI041 [*] Use `complex` instead of `int | float | complex` + | +90 | ... +91 | +92 | def bad5(self, arg: "int | (float | complex)") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +93 | ... + | + = help: Remove redundant type + +ℹ Safe fix +89 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": +90 90 | ... +91 91 | +92 |- def bad5(self, arg: "int | (float | complex)") -> "None": + 92 |+ def bad5(self, arg: "complex") -> "None": +93 93 | ... +94 94 | +95 95 | + +PYI041_3.py:99:24: PYI041 Use `float` instead of `int | float` + | + 97 | # fix must not yield runtime `None | None | ...` (TypeError) + 98 | class Issue18298: + 99 | def f1(self, arg: "None | int | None | float" = None) -> "None": # PYI041 - no fix + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +100 | pass + | + = help: Remove redundant type + +PYI041_3.py:104:28: PYI041 [*] Use `float` instead of `int | float` + | +102 | if TYPE_CHECKING: +103 | +104 | def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +105 | +106 | else: + | + = help: Remove redundant type + +ℹ Safe fix +101 101 | +102 102 | if TYPE_CHECKING: +103 103 | +104 |- def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix + 104 |+ def f2(self, arg: "None | None | float" = None) -> "None": ... # PYI041 - with fix +105 105 | +106 106 | else: +107 107 | + +PYI041_3.py:111:24: PYI041 [*] Use `float` instead of `int | float` + | +109 | pass +110 | +111 | def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +112 | pass + | + = help: Remove redundant type + +ℹ Safe fix +108 108 | def f2(self, arg=None) -> "None": +109 109 | pass +110 110 | +111 |- def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix + 111 |+ def f3(self, arg: "None | float | None | None" = None) -> "None": # PYI041 - with fix +112 112 | pass From 18d2b2f0eee1de78890837a8dbcc97fabda411d0 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Sat, 11 Oct 2025 10:05:01 +0200 Subject: [PATCH 2/4] Skip fix offering for concatenated stringinzed annotations Similar to RUF013 --- .../test/fixtures/flake8_pyi/PYI041_3.py | 25 +++++ .../test/fixtures/flake8_pyi/PYI041_4.py | 8 ++ .../ruff_linter/src/rules/flake8_pyi/mod.rs | 1 + .../rules/redundant_numeric_union.rs | 22 +++-- ...flake8_pyi__tests__PYI041_PYI041_3.py.snap | 98 ++++++++++++++++--- ...flake8_pyi__tests__PYI041_PYI041_4.py.snap | 70 +++++++++++++ ..._ruff__tests__PY39_RUF013_RUF013_0.py.snap | 1 - ...ules__ruff__tests__RUF013_RUF013_0.py.snap | 1 - 8 files changed, 198 insertions(+), 28 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_4.py create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py index 79cd0f2ce6..42d44756b6 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py @@ -110,3 +110,28 @@ class Issue18298: def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix pass + + +class FooStringConcat: + def good(self, arg: "i" "nt") -> "None": + ... + + def bad(self, arg: "int " "| float | com" "plex") -> "None": + ... + + def bad2(self, arg: "int | Union[flo" "at, complex]") -> "None": + ... + + def bad3(self, arg: "Union[Union[float, com" "plex], int]") -> "None": + ... + + def bad4(self, arg: "Union[float | complex, in" "t ]") -> "None": + ... + + def bad5(self, arg: "int | " + "(float | complex)") -> "None": + ... + + def bad6(self, arg: "in\ +t | (float | compl" "ex)") -> "None": + ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_4.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_4.py new file mode 100644 index 0000000000..d0e3467a1e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_4.py @@ -0,0 +1,8 @@ +from typing import Union as Uno + + +def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +def f2(a: "Uno[int, float, Foo]") -> "None": ... +def f3(a: """Uno[int, float, Foo]""") -> "None": ... +def f4(a: "Uno[in\ +t, float, Foo]") -> "None": ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index 1c0c0408ac..fecc7836b0 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -77,6 +77,7 @@ mod tests { #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_1.pyi"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_2.py"))] #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_3.py"))] + #[test_case(Rule::RedundantNumericUnion, Path::new("PYI041_4.py"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.py"))] #[test_case(Rule::SnakeCaseTypeAlias, Path::new("PYI042.pyi"))] #[test_case(Rule::StrOrReprDefinedInStub, Path::new("PYI029.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 27b37e2e36..fc5ad100f9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -143,18 +143,20 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr, unresolved_anno return; } - let string_annotation = unresolved_annotation - .as_string_literal_expr() - .map(|str| str.value.to_str()); + let string_annotation = unresolved_annotation.as_string_literal_expr(); + if string_annotation.is_some_and(|s| s.value.is_implicit_concatenated()) { + // No fix for concatenated string literals. They're rare and too complex to handle. + // https://github.com/astral-sh/ruff/issues/19184#issuecomment-3047695205 + return; + } // Mark [`Fix`] as unsafe when comments are in range. - let applicability = if string_annotation.is_some_and(|s| s.contains('#')) - || checker.comment_ranges().intersects(annotation.range()) - { - Applicability::Unsafe - } else { - Applicability::Safe - }; + let applicability = + if string_annotation.is_some() || checker.comment_ranges().intersects(annotation.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; // Generate the flattened fix once. let fix = if let &[edit_expr] = necessary_nodes.as_slice() { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap index b88edb0a6e..23e8ec7631 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap @@ -9,7 +9,7 @@ PYI041_3.py:23:15: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 20 20 | ... 21 21 | 22 22 | @@ -27,7 +27,7 @@ PYI041_3.py:27:33: PYI041 [*] Use `complex` instead of `float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 24 24 | ... 25 25 | 26 26 | @@ -45,7 +45,7 @@ PYI041_3.py:31:31: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 28 28 | ... 29 29 | 30 30 | @@ -63,7 +63,7 @@ PYI041_3.py:35:29: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 32 32 | ... 33 33 | 34 34 | @@ -81,7 +81,7 @@ PYI041_3.py:39:25: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 36 36 | ... 37 37 | 38 38 | @@ -99,7 +99,7 @@ PYI041_3.py:43:29: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 40 40 | ... 41 41 | 42 42 | @@ -117,7 +117,7 @@ PYI041_3.py:47:29: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 44 44 | ... 45 45 | 46 46 | @@ -135,7 +135,7 @@ PYI041_3.py:51:29: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 48 48 | ... 49 49 | 50 50 | @@ -153,7 +153,7 @@ PYI041_3.py:55:29: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 52 52 | ... 53 53 | 54 54 | @@ -221,7 +221,7 @@ PYI041_3.py:80:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 77 77 | def good(self, arg: "int") -> "None": 78 78 | ... 79 79 | @@ -241,7 +241,7 @@ PYI041_3.py:83:26: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 80 80 | def bad(self, arg: "int | float | complex") -> "None": 81 81 | ... 82 82 | @@ -261,7 +261,7 @@ PYI041_3.py:86:26: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 83 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": 84 84 | ... 85 85 | @@ -281,7 +281,7 @@ PYI041_3.py:89:26: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 86 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": 87 87 | ... 88 88 | @@ -301,7 +301,7 @@ PYI041_3.py:92:26: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 89 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": 90 90 | ... 91 91 | @@ -332,7 +332,7 @@ PYI041_3.py:104:28: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 101 101 | 102 102 | if TYPE_CHECKING: 103 103 | @@ -352,10 +352,76 @@ PYI041_3.py:111:24: PYI041 [*] Use `float` instead of `int | float` | = help: Remove redundant type -ℹ Safe fix +ℹ Unsafe fix 108 108 | def f2(self, arg=None) -> "None": 109 109 | pass 110 110 | 111 |- def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix 111 |+ def f3(self, arg: "None | float | None | None" = None) -> "None": # PYI041 - with fix 112 112 | pass +113 113 | +114 114 | + +PYI041_3.py:119:24: PYI041 Use `complex` instead of `int | float | complex` + | +117 | ... +118 | +119 | def bad(self, arg: "int " "| float | com" "plex") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +120 | ... + | + = help: Remove redundant type + +PYI041_3.py:122:25: PYI041 Use `complex` instead of `int | float | complex` + | +120 | ... +121 | +122 | def bad2(self, arg: "int | Union[flo" "at, complex]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +123 | ... + | + = help: Remove redundant type + +PYI041_3.py:125:25: PYI041 Use `complex` instead of `int | float | complex` + | +123 | ... +124 | +125 | def bad3(self, arg: "Union[Union[float, com" "plex], int]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +126 | ... + | + = help: Remove redundant type + +PYI041_3.py:128:25: PYI041 Use `complex` instead of `int | float | complex` + | +126 | ... +127 | +128 | def bad4(self, arg: "Union[float | complex, in" "t ]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +129 | ... + | + = help: Remove redundant type + +PYI041_3.py:131:25: PYI041 Use `complex` instead of `int | float | complex` + | +129 | ... +130 | +131 | def bad5(self, arg: "int | " + | _________________________^ +132 | | "(float | complex)") -> "None": + | |___________________________________________^ PYI041 +133 | ... + | + = help: Remove redundant type + +PYI041_3.py:135:25: PYI041 Use `complex` instead of `int | float | complex` + | +133 | ... +134 | +135 | def bad6(self, arg: "in\ + | _________________________^ +136 | | t | (float | compl" "ex)") -> "None": + | |_________________________^ PYI041 +137 | ... + | + = help: Remove redundant type diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap new file mode 100644 index 0000000000..01b0ffccb9 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap @@ -0,0 +1,70 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI041_4.py:4:11: PYI041 Use `float` instead of `int | float` + | +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... + | + = help: Remove redundant type + +PYI041_4.py:5:12: PYI041 [*] Use `float` instead of `int | float` + | +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... + | ^^^^^^^^^^^^^^^^^^^^ PYI041 +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 | def f4(a: "Uno[in\ + | + = help: Remove redundant type + +ℹ Unsafe fix +2 2 | +3 3 | +4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 |-def f2(a: "Uno[int, float, Foo]") -> "None": ... + 5 |+def f2(a: "Uno[float, Foo]") -> "None": ... +6 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 7 | def f4(a: "Uno[in\ +8 8 | t, float, Foo]") -> "None": ... + +PYI041_4.py:6:14: PYI041 [*] Use `float` instead of `int | float` + | +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... + | ^^^^^^^^^^^^^^^^^^^^ PYI041 +7 | def f4(a: "Uno[in\ +8 | t, float, Foo]") -> "None": ... + | + = help: Remove redundant type + +ℹ Unsafe fix +3 3 | +4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 |-def f3(a: """Uno[int, float, Foo]""") -> "None": ... + 6 |+def f3(a: """Uno[float, Foo]""") -> "None": ... +7 7 | def f4(a: "Uno[in\ +8 8 | t, float, Foo]") -> "None": ... + +PYI041_4.py:7:11: PYI041 [*] Use `float` instead of `int | float` + | +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 | def f4(a: "Uno[in\ + | ___________^ +8 | | t, float, Foo]") -> "None": ... + | |_______________^ PYI041 + | + = help: Remove redundant type + +ℹ Unsafe fix +4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 |-def f4(a: "Uno[in\ +8 |-t, float, Foo]") -> "None": ... + 7 |+def f4(a: Uno[float, Foo]) -> "None": ... diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap index 2617077336..096eceb46b 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- RUF013_0.py:20:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_0.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_0.py.snap index ded2a1770c..50bcc3df25 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_0.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF013_RUF013_0.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- RUF013_0.py:20:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | From e57142351e9b626fb3c4b553cdfd04202a062d4d Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Sat, 11 Oct 2025 10:18:33 +0200 Subject: [PATCH 3/4] Simplify code --- crates/ruff_linter/src/checkers/ast/mod.rs | 23 ++++++++----------- .../rules/redundant_numeric_union.rs | 8 ++++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index cd998ac9d4..fbb86a88d7 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -538,24 +538,21 @@ impl<'a> Checker<'a> { } } - /// Apply a mapper function to an annotation expression, - /// abstracting over the fact that the annotation expression might be "stringized". + /// Given a type annotation [`Expr`], abstracting over the fact that the annotation expression + /// might be "stringized". /// /// A stringized annotation is one enclosed in string quotes: /// `foo: "typing.Any"` means the same thing to a type checker as `foo: typing.Any`. - pub(crate) fn map_maybe_stringized_annotation( - &self, - expr: &ast::Expr, - map_fn: impl FnOnce(&ast::Expr) -> T, - ) -> T { + pub(crate) fn map_maybe_stringized_annotation<'b>(&self, expr: &'b ast::Expr) -> &'b ast::Expr + where + 'a: 'b, + { if let ast::Expr::StringLiteral(string_annotation) = expr { - let Some(parsed_annotation) = self.parse_type_annotation(string_annotation).ok() else { - return map_fn(expr); - }; - map_fn(parsed_annotation.expression()) - } else { - map_fn(expr) + if let Ok(parsed_annotation) = self.parse_type_annotation(string_annotation) { + return parsed_annotation.expression(); + } } + expr } /// Push `diagnostic` if the checker is not in a `@no_type_check` context. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index fc5ad100f9..44ad689dbe 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -80,9 +80,11 @@ impl Violation for RedundantNumericUnion { /// PYI041 pub(crate) fn redundant_numeric_union(checker: &Checker, parameters: &Parameters) { for annotation in parameters.iter().filter_map(AnyParameterRef::annotation) { - checker.map_maybe_stringized_annotation(annotation, |resolved_annotation| { - check_annotation(checker, resolved_annotation, annotation); - }); + check_annotation( + checker, + checker.map_maybe_stringized_annotation(annotation), + annotation, + ); } } From 18e90f8b50e39b31e09f359accc7faca1d5f1632 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Sat, 11 Oct 2025 10:44:31 +0200 Subject: [PATCH 4/4] Fix merge (changed reporting format) --- ...flake8_pyi__tests__PYI041_PYI041_3.py.snap | 521 +++++++++--------- ...flake8_pyi__tests__PYI041_PYI041_4.py.snap | 79 +-- 2 files changed, 304 insertions(+), 296 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap index 23e8ec7631..22cdbae552 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_3.py.snap @@ -1,427 +1,434 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041_3.py:23:15: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:23:15 | 23 | def f0(arg1: "float | int") -> "None": - | ^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^ 24 | ... | - = help: Remove redundant type +help: Remove redundant type +20 | ... +21 | +22 | + - def f0(arg1: "float | int") -> "None": +23 + def f0(arg1: "float") -> "None": +24 | ... +25 | +26 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -20 20 | ... -21 21 | -22 22 | -23 |-def f0(arg1: "float | int") -> "None": - 23 |+def f0(arg1: "float") -> "None": -24 24 | ... -25 25 | -26 26 | - -PYI041_3.py:27:33: PYI041 [*] Use `complex` instead of `float | complex` +PYI041 [*] Use `complex` instead of `float | complex` + --> PYI041_3.py:27:33 | 27 | def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | ... | - = help: Remove redundant type +help: Remove redundant type +24 | ... +25 | +26 | + - def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": +27 + def f1(arg1: "float", *, arg2: "list[str] | type[bool] | complex") -> "None": +28 | ... +29 | +30 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -24 24 | ... -25 25 | -26 26 | -27 |-def f1(arg1: "float", *, arg2: "float | list[str] | type[bool] | complex") -> "None": - 27 |+def f1(arg1: "float", *, arg2: "list[str] | type[bool] | complex") -> "None": -28 28 | ... -29 29 | -30 30 | - -PYI041_3.py:31:31: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:31:31 | 31 | def f2(arg1: "int", /, arg2: "int | int | float") -> "None": - | ^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^ 32 | ... | - = help: Remove redundant type +help: Remove redundant type +28 | ... +29 | +30 | + - def f2(arg1: "int", /, arg2: "int | int | float") -> "None": +31 + def f2(arg1: "int", /, arg2: "float") -> "None": +32 | ... +33 | +34 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -28 28 | ... -29 29 | -30 30 | -31 |-def f2(arg1: "int", /, arg2: "int | int | float") -> "None": - 31 |+def f2(arg1: "int", /, arg2: "float") -> "None": -32 32 | ... -33 33 | -34 34 | - -PYI041_3.py:35:29: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:35:29 | 35 | def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^ 36 | ... | - = help: Remove redundant type +help: Remove redundant type +32 | ... +33 | +34 | + - def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": +35 + def f3(arg1: "int", *args: "float") -> "None": +36 | ... +37 | +38 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -32 32 | ... -33 33 | -34 34 | -35 |-def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": - 35 |+def f3(arg1: "int", *args: "float") -> "None": -36 36 | ... -37 37 | -38 38 | - -PYI041_3.py:39:25: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:39:25 | 39 | async def f4(**kwargs: "int | int | float") -> "None": - | ^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^ 40 | ... | - = help: Remove redundant type +help: Remove redundant type +36 | ... +37 | +38 | + - async def f4(**kwargs: "int | int | float") -> "None": +39 + async def f4(**kwargs: "float") -> "None": +40 | ... +41 | +42 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -36 36 | ... -37 37 | -38 38 | -39 |-async def f4(**kwargs: "int | int | float") -> "None": - 39 |+async def f4(**kwargs: "float") -> "None": -40 40 | ... -41 41 | -42 42 | - -PYI041_3.py:43:29: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:43:29 | 43 | def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^ 44 | ... | - = help: Remove redundant type +help: Remove redundant type +40 | ... +41 | +42 | + - def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": +43 + def f5(arg1: "int", *args: "float") -> "None": +44 | ... +45 | +46 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -40 40 | ... -41 41 | -42 42 | -43 |-def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": - 43 |+def f5(arg1: "int", *args: "float") -> "None": -44 44 | ... -45 45 | -46 46 | - -PYI041_3.py:47:29: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:47:29 | 47 | def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 48 | ... | - = help: Remove redundant type +help: Remove redundant type +44 | ... +45 | +46 | + - def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": +47 + def f6(arg1: "int", *args: "float") -> "None": +48 | ... +49 | +50 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -44 44 | ... -45 45 | -46 46 | -47 |-def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": - 47 |+def f6(arg1: "int", *args: "float") -> "None": -48 48 | ... -49 49 | -50 50 | - -PYI041_3.py:51:29: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:51:29 | 51 | def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | ... | - = help: Remove redundant type +help: Remove redundant type +48 | ... +49 | +50 | + - def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": +51 + def f7(arg1: "int", *args: "float") -> "None": +52 | ... +53 | +54 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -48 48 | ... -49 49 | -50 50 | -51 |-def f7(arg1: "int", *args: "Union[Union[Union[int, int, float]]]") -> "None": - 51 |+def f7(arg1: "int", *args: "float") -> "None": -52 52 | ... -53 53 | -54 54 | - -PYI041_3.py:55:29: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:55:29 | 55 | def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 56 | ... | - = help: Remove redundant type +help: Remove redundant type +52 | ... +53 | +54 | + - def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": +55 + def f8(arg1: "int", *args: "float") -> "None": +56 | ... +57 | +58 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -52 52 | ... -53 53 | -54 54 | -55 |-def f8(arg1: "int", *args: "Union[Union[Union[int | int | float]]]") -> "None": - 55 |+def f8(arg1: "int", *args: "float") -> "None": -56 56 | ... -57 57 | -58 58 | - -PYI041_3.py:60:13: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:60:13 | 59 | def f9( 60 | arg: """Union[ # comment | _____________^ 61 | | float, # another 62 | | complex, int]""" - | |_____________________^ PYI041 + | |_____________________^ 63 | ) -> "None": 64 | ... | - = help: Remove redundant type +help: Remove redundant type +57 | +58 | +59 | def f9( + - arg: """Union[ # comment + - float, # another + - complex, int]""" +60 + arg: """complex""" +61 | ) -> "None": +62 | ... +63 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -57 57 | -58 58 | -59 59 | def f9( -60 |- arg: """Union[ # comment -61 |- float, # another -62 |- complex, int]""" - 60 |+ arg: """complex""" -63 61 | ) -> "None": -64 62 | ... -65 63 | - -PYI041_3.py:68:9: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:68:9 | 66 | def f10( 67 | arg: """ 68 | / int | # comment 69 | | float | # another 70 | | complex - | |_______________^ PYI041 + | |_______________^ 71 | """ 72 | ) -> "None": | - = help: Remove redundant type +help: Remove redundant type +65 | +66 | def f10( +67 | arg: """ + - int | # comment + - float | # another +68 | complex +69 | """ +70 | ) -> "None": +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -65 65 | -66 66 | def f10( -67 67 | arg: """ -68 |- int | # comment -69 |- float | # another -70 68 | complex -71 69 | """ -72 70 | ) -> "None": - -PYI041_3.py:80:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:80:25 | 78 | ... 79 | 80 | def bad(self, arg: "int | float | complex") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^ 81 | ... | - = help: Remove redundant type +help: Remove redundant type +77 | def good(self, arg: "int") -> "None": +78 | ... +79 | + - def bad(self, arg: "int | float | complex") -> "None": +80 + def bad(self, arg: "complex") -> "None": +81 | ... +82 | +83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -77 77 | def good(self, arg: "int") -> "None": -78 78 | ... -79 79 | -80 |- def bad(self, arg: "int | float | complex") -> "None": - 80 |+ def bad(self, arg: "complex") -> "None": -81 81 | ... -82 82 | -83 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": - -PYI041_3.py:83:26: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:83:26 | 81 | ... 82 | 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 84 | ... | - = help: Remove redundant type +help: Remove redundant type +80 | def bad(self, arg: "int | float | complex") -> "None": +81 | ... +82 | + - def bad2(self, arg: "int | Union[float, complex]") -> "None": +83 + def bad2(self, arg: "complex") -> "None": +84 | ... +85 | +86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -80 80 | def bad(self, arg: "int | float | complex") -> "None": -81 81 | ... -82 82 | -83 |- def bad2(self, arg: "int | Union[float, complex]") -> "None": - 83 |+ def bad2(self, arg: "complex") -> "None": -84 84 | ... -85 85 | -86 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": - -PYI041_3.py:86:26: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:86:26 | 84 | ... 85 | 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 87 | ... | - = help: Remove redundant type +help: Remove redundant type +83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": +84 | ... +85 | + - def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": +86 + def bad3(self, arg: "complex") -> "None": +87 | ... +88 | +89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -83 83 | def bad2(self, arg: "int | Union[float, complex]") -> "None": -84 84 | ... -85 85 | -86 |- def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": - 86 |+ def bad3(self, arg: "complex") -> "None": -87 87 | ... -88 88 | -89 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": - -PYI041_3.py:89:26: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:89:26 | 87 | ... 88 | 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 90 | ... | - = help: Remove redundant type +help: Remove redundant type +86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": +87 | ... +88 | + - def bad4(self, arg: "Union[float | complex, int]") -> "None": +89 + def bad4(self, arg: "complex") -> "None": +90 | ... +91 | +92 | def bad5(self, arg: "int | (float | complex)") -> "None": +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -86 86 | def bad3(self, arg: "Union[Union[float, complex], int]") -> "None": -87 87 | ... -88 88 | -89 |- def bad4(self, arg: "Union[float | complex, int]") -> "None": - 89 |+ def bad4(self, arg: "complex") -> "None": -90 90 | ... -91 91 | -92 92 | def bad5(self, arg: "int | (float | complex)") -> "None": - -PYI041_3.py:92:26: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:92:26 | 90 | ... 91 | 92 | def bad5(self, arg: "int | (float | complex)") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^ 93 | ... | - = help: Remove redundant type +help: Remove redundant type +89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": +90 | ... +91 | + - def bad5(self, arg: "int | (float | complex)") -> "None": +92 + def bad5(self, arg: "complex") -> "None": +93 | ... +94 | +95 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -89 89 | def bad4(self, arg: "Union[float | complex, int]") -> "None": -90 90 | ... -91 91 | -92 |- def bad5(self, arg: "int | (float | complex)") -> "None": - 92 |+ def bad5(self, arg: "complex") -> "None": -93 93 | ... -94 94 | -95 95 | - -PYI041_3.py:99:24: PYI041 Use `float` instead of `int | float` +PYI041 Use `float` instead of `int | float` + --> PYI041_3.py:99:24 | 97 | # fix must not yield runtime `None | None | ...` (TypeError) 98 | class Issue18298: 99 | def f1(self, arg: "None | int | None | float" = None) -> "None": # PYI041 - no fix - | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ 100 | pass | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:104:28: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:104:28 | 102 | if TYPE_CHECKING: 103 | 104 | def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix - | ^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ 105 | 106 | else: | - = help: Remove redundant type +help: Remove redundant type +101 | +102 | if TYPE_CHECKING: +103 | + - def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix +104 + def f2(self, arg: "None | None | float" = None) -> "None": ... # PYI041 - with fix +105 | +106 | else: +107 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -101 101 | -102 102 | if TYPE_CHECKING: -103 103 | -104 |- def f2(self, arg: "None | int | None | float" = None) -> "None": ... # PYI041 - with fix - 104 |+ def f2(self, arg: "None | None | float" = None) -> "None": ... # PYI041 - with fix -105 105 | -106 106 | else: -107 107 | - -PYI041_3.py:111:24: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:111:24 | 109 | pass 110 | 111 | def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 112 | pass | - = help: Remove redundant type +help: Remove redundant type +108 | def f2(self, arg=None) -> "None": +109 | pass +110 | + - def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix +111 + def f3(self, arg: "None | float | None | None" = None) -> "None": # PYI041 - with fix +112 | pass +113 | +114 | +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -108 108 | def f2(self, arg=None) -> "None": -109 109 | pass -110 110 | -111 |- def f3(self, arg: "None | float | None | int | None" = None) -> "None": # PYI041 - with fix - 111 |+ def f3(self, arg: "None | float | None | None" = None) -> "None": # PYI041 - with fix -112 112 | pass -113 113 | -114 114 | - -PYI041_3.py:119:24: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:119:24 | 117 | ... 118 | 119 | def bad(self, arg: "int " "| float | com" "plex") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 120 | ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:122:25: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:122:25 | 120 | ... 121 | 122 | def bad2(self, arg: "int | Union[flo" "at, complex]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 123 | ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:125:25: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:125:25 | 123 | ... 124 | 125 | def bad3(self, arg: "Union[Union[float, com" "plex], int]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 126 | ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:128:25: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:128:25 | 126 | ... 127 | 128 | def bad4(self, arg: "Union[float | complex, in" "t ]") -> "None": - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 129 | ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:131:25: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:131:25 | 129 | ... 130 | 131 | def bad5(self, arg: "int | " | _________________________^ 132 | | "(float | complex)") -> "None": - | |___________________________________________^ PYI041 + | |___________________________________________^ 133 | ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_3.py:135:25: PYI041 Use `complex` instead of `int | float | complex` +PYI041 Use `complex` instead of `int | float | complex` + --> PYI041_3.py:135:25 | 133 | ... 134 | 135 | def bad6(self, arg: "in\ | _________________________^ 136 | | t | (float | compl" "ex)") -> "None": - | |_________________________^ PYI041 + | |_________________________^ 137 | ... | - = help: Remove redundant type +help: Remove redundant type diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap index 01b0ffccb9..e019c11f0d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041_4.py.snap @@ -1,70 +1,71 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041_4.py:4:11: PYI041 Use `float` instead of `int | float` +PYI041 Use `float` instead of `int | float` + --> PYI041_4.py:4:11 | 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... | - = help: Remove redundant type +help: Remove redundant type -PYI041_4.py:5:12: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_4.py:5:12 | 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... - | ^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^ 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... 7 | def f4(a: "Uno[in\ | - = help: Remove redundant type +help: Remove redundant type +2 | +3 | +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... + - def f2(a: "Uno[int, float, Foo]") -> "None": ... +5 + def f2(a: "Uno[float, Foo]") -> "None": ... +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 | def f4(a: "Uno[in\ +8 | t, float, Foo]") -> "None": ... +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -2 2 | -3 3 | -4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... -5 |-def f2(a: "Uno[int, float, Foo]") -> "None": ... - 5 |+def f2(a: "Uno[float, Foo]") -> "None": ... -6 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... -7 7 | def f4(a: "Uno[in\ -8 8 | t, float, Foo]") -> "None": ... - -PYI041_4.py:6:14: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_4.py:6:14 | 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... - | ^^^^^^^^^^^^^^^^^^^^ PYI041 + | ^^^^^^^^^^^^^^^^^^^^ 7 | def f4(a: "Uno[in\ 8 | t, float, Foo]") -> "None": ... | - = help: Remove redundant type +help: Remove redundant type +3 | +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... + - def f3(a: """Uno[int, float, Foo]""") -> "None": ... +6 + def f3(a: """Uno[float, Foo]""") -> "None": ... +7 | def f4(a: "Uno[in\ +8 | t, float, Foo]") -> "None": ... +note: This is an unsafe fix and may change runtime behavior -ℹ Unsafe fix -3 3 | -4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... -5 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... -6 |-def f3(a: """Uno[int, float, Foo]""") -> "None": ... - 6 |+def f3(a: """Uno[float, Foo]""") -> "None": ... -7 7 | def f4(a: "Uno[in\ -8 8 | t, float, Foo]") -> "None": ... - -PYI041_4.py:7:11: PYI041 [*] Use `float` instead of `int | float` +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_4.py:7:11 | 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... 7 | def f4(a: "Uno[in\ | ___________^ 8 | | t, float, Foo]") -> "None": ... - | |_______________^ PYI041 + | |_______________^ | - = help: Remove redundant type - -ℹ Unsafe fix -4 4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... -5 5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... -6 6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... -7 |-def f4(a: "Uno[in\ -8 |-t, float, Foo]") -> "None": ... - 7 |+def f4(a: Uno[float, Foo]) -> "None": ... +help: Remove redundant type +4 | def f1(a: "U" "no[int, fl" "oat, Foo]") -> "None": ... +5 | def f2(a: "Uno[int, float, Foo]") -> "None": ... +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... + - def f4(a: "Uno[in\ + - t, float, Foo]") -> "None": ... +7 + def f4(a: Uno[float, Foo]) -> "None": ... +note: This is an unsafe fix and may change runtime behavior