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..42d44756b6 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041_3.py @@ -0,0 +1,137 @@ +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 + + +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/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 5f7f459ba9..5c37239764 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -543,6 +543,23 @@ impl<'a> Checker<'a> { } } + /// 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<'b>(&self, expr: &'b ast::Expr) -> &'b ast::Expr + where + 'a: 'b, + { + if let ast::Expr::StringLiteral(string_annotation) = 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. 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 1248e3c029..8a9aba9be5 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -76,6 +76,8 @@ 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::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 1e4583f312..7a748d8bb2 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 @@ -81,11 +81,15 @@ 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); + check_annotation( + checker, + checker.map_maybe_stringized_annotation(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| { @@ -142,12 +146,20 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) { return; } + 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 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 new file mode 100644 index 0000000000..22cdbae552 --- /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,434 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:23:15 + | +23 | def f0(arg1: "float | int") -> "None": + | ^^^^^^^^^^^ +24 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +28 | ... + | +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 + +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:31:31 + | +31 | def f2(arg1: "int", /, arg2: "int | int | float") -> "None": + | ^^^^^^^^^^^^^^^^^ +32 | ... + | +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 + +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:35:29 + | +35 | def f3(arg1: "int", *args: "Union[int | int | float]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^ +36 | ... + | +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 + +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:39:25 + | +39 | async def f4(**kwargs: "int | int | float") -> "None": + | ^^^^^^^^^^^^^^^^^ +40 | ... + | +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 + +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:43:29 + | +43 | def f5(arg1: "int", *args: "Union[int, int, float]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^ +44 | ... + | +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 + +PYI041 [*] Use `float` instead of `int | float` + --> PYI041_3.py:47:29 + | +47 | def f6(arg1: "int", *args: "Union[Union[int, int, float]]") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +48 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +52 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +56 | ... + | +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 + +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]""" + | |_____________________^ +63 | ) -> "None": +64 | ... + | +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 + +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 + | |_______________^ +71 | """ +72 | ) -> "None": + | +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 + +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:80:25 + | +78 | ... +79 | +80 | def bad(self, arg: "int | float | complex") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^ +81 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +84 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +87 | ... + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +90 | ... + | +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 + +PYI041 [*] Use `complex` instead of `int | float | complex` + --> PYI041_3.py:92:26 + | +90 | ... +91 | +92 | def bad5(self, arg: "int | (float | complex)") -> "None": + | ^^^^^^^^^^^^^^^^^^^^^^^ +93 | ... + | +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 + +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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +100 | pass + | +help: Remove redundant type + +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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +105 | +106 | else: + | +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 + +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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +112 | pass + | +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 + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +120 | ... + | +help: Remove redundant type + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +123 | ... + | +help: Remove redundant type + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +126 | ... + | +help: Remove redundant type + +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": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +129 | ... + | +help: Remove redundant type + +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": + | |___________________________________________^ +133 | ... + | +help: Remove redundant type + +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": + | |_________________________^ +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..e019c11f0d --- /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,71 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI041 Use `float` instead of `int | float` + --> PYI041_4.py:4:11 + | +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": ... + | +help: Remove redundant type + +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": ... + | ^^^^^^^^^^^^^^^^^^^^ +6 | def f3(a: """Uno[int, float, Foo]""") -> "None": ... +7 | def f4(a: "Uno[in\ + | +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 + +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": ... + | ^^^^^^^^^^^^^^^^^^^^ +7 | def f4(a: "Uno[in\ +8 | t, float, Foo]") -> "None": ... + | +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 + +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": ... + | |_______________^ + | +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