Skip fix offering for concatenated stringinzed annotations

Similar to RUF013
This commit is contained in:
Robsdedude 2025-10-11 10:05:01 +02:00
parent b7d7160839
commit 18d2b2f0ee
8 changed files with 198 additions and 28 deletions

View File

@ -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":
...

View File

@ -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": ...

View File

@ -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"))]

View File

@ -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() {

View File

@ -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

View File

@ -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": ...

View File

@ -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`
|

View File

@ -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`
|