Skip to content

Commit 519a650

Browse files
Mark quotes as unnecessary for non-evaluated annotations (#11485)
## Summary Similar to #11414, this PR extends `UP037` to flag quoted annotations that are located in positions that won't be evaluated at runtime. For example, the quotes on `Tuple` are unnecessary in: ```python from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Tuple def foo(): x: "Tuple[int, int]" = (0, 0) foo() ```
1 parent 573facd commit 519a650

File tree

8 files changed

+88
-34
lines changed

8 files changed

+88
-34
lines changed

crates/ruff/tests/integration_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1414,7 +1414,7 @@ fn check_input_from_argfile() -> Result<()> {
14141414
fs::write(&file_a_path, b"import os")?;
14151415
fs::write(&file_b_path, b"print('hello, world!')")?;
14161416

1417-
// Create a the input file for argfile to expand
1417+
// Create the input file for argfile to expand
14181418
let input_file_path = tempdir.path().join("file_paths.txt");
14191419
fs::write(
14201420
&input_file_path,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from typing import TYPE_CHECKING
2+
3+
if TYPE_CHECKING:
4+
from typing import Tuple
5+
6+
7+
def foo():
8+
# UP037
9+
x: "Tuple[int, int]" = (0, 0)
10+
print(x)
11+
12+
13+
# OK
14+
X: "Tuple[int, int]" = (0, 0)

crates/ruff_linter/src/checkers/ast/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,7 @@ impl<'a> Checker<'a> {
21522152

21532153
self.semantic.restore(snapshot);
21542154

2155-
if self.semantic.in_annotation() && self.semantic.future_annotations_or_stub() {
2155+
if self.semantic.in_annotation() && self.semantic.in_typing_only_annotation() {
21562156
if self.enabled(Rule::QuotedAnnotation) {
21572157
pyupgrade::rules::quoted_annotation(self, value, range);
21582158
}

crates/ruff_linter/src/rules/pyupgrade/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ mod tests {
5555
#[test_case(Rule::OutdatedVersionBlock, Path::new("UP036_5.py"))]
5656
#[test_case(Rule::PrintfStringFormatting, Path::new("UP031_0.py"))]
5757
#[test_case(Rule::PrintfStringFormatting, Path::new("UP031_1.py"))]
58-
#[test_case(Rule::QuotedAnnotation, Path::new("UP037.py"))]
58+
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_0.py"))]
59+
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_1.py"))]
5960
#[test_case(Rule::RedundantOpenModes, Path::new("UP015.py"))]
6061
#[test_case(Rule::ReplaceStdoutStderr, Path::new("UP022.py"))]
6162
#[test_case(Rule::ReplaceUniversalNewlines, Path::new("UP021.py"))]

crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs

+19
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ use crate::checkers::ast::Checker;
1010
///
1111
/// ## Why is this bad?
1212
/// In Python, type annotations can be quoted to avoid forward references.
13+
///
1314
/// However, if `from __future__ import annotations` is present, Python
1415
/// will always evaluate type annotations in a deferred manner, making
1516
/// the quotes unnecessary.
1617
///
18+
/// Similarly, if the annotation is located in a typing-only context and
19+
/// won't be evaluated by Python at runtime, the quotes will also be
20+
/// considered unnecessary. For example, Python does not evaluate type
21+
/// annotations on assignments in function bodies.
22+
///
1723
/// ## Example
24+
/// Given:
1825
/// ```python
1926
/// from __future__ import annotations
2027
///
@@ -32,6 +39,18 @@ use crate::checkers::ast::Checker;
3239
/// ...
3340
/// ```
3441
///
42+
/// Given:
43+
/// ```python
44+
/// def foo() -> None:
45+
/// bar: "Bar"
46+
/// ```
47+
///
48+
/// Use instead:
49+
/// ```python
50+
/// def foo() -> None:
51+
/// bar: Bar
52+
/// ```
53+
///
3554
/// ## References
3655
/// - [PEP 563](https://peps.python.org/pep-0563/)
3756
/// - [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)

crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP037.py.snap renamed to crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP037_0.py.snap

+29-31
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
33
---
4-
UP037.py:18:14: UP037 [*] Remove quotes from type annotation
4+
UP037_0.py:18:14: UP037 [*] Remove quotes from type annotation
55
|
66
18 | def foo(var: "MyClass") -> "MyClass":
77
| ^^^^^^^^^ UP037
@@ -19,7 +19,7 @@ UP037.py:18:14: UP037 [*] Remove quotes from type annotation
1919
20 20 |
2020
21 21 |
2121

22-
UP037.py:18:28: UP037 [*] Remove quotes from type annotation
22+
UP037_0.py:18:28: UP037 [*] Remove quotes from type annotation
2323
|
2424
18 | def foo(var: "MyClass") -> "MyClass":
2525
| ^^^^^^^^^ UP037
@@ -37,7 +37,7 @@ UP037.py:18:28: UP037 [*] Remove quotes from type annotation
3737
20 20 |
3838
21 21 |
3939

40-
UP037.py:19:8: UP037 [*] Remove quotes from type annotation
40+
UP037_0.py:19:8: UP037 [*] Remove quotes from type annotation
4141
|
4242
18 | def foo(var: "MyClass") -> "MyClass":
4343
19 | x: "MyClass"
@@ -55,7 +55,7 @@ UP037.py:19:8: UP037 [*] Remove quotes from type annotation
5555
21 21 |
5656
22 22 | def foo(*, inplace: "bool"):
5757

58-
UP037.py:22:21: UP037 [*] Remove quotes from type annotation
58+
UP037_0.py:22:21: UP037 [*] Remove quotes from type annotation
5959
|
6060
22 | def foo(*, inplace: "bool"):
6161
| ^^^^^^ UP037
@@ -73,7 +73,7 @@ UP037.py:22:21: UP037 [*] Remove quotes from type annotation
7373
24 24 |
7474
25 25 |
7575

76-
UP037.py:26:16: UP037 [*] Remove quotes from type annotation
76+
UP037_0.py:26:16: UP037 [*] Remove quotes from type annotation
7777
|
7878
26 | def foo(*args: "str", **kwargs: "int"):
7979
| ^^^^^ UP037
@@ -91,7 +91,7 @@ UP037.py:26:16: UP037 [*] Remove quotes from type annotation
9191
28 28 |
9292
29 29 |
9393

94-
UP037.py:26:33: UP037 [*] Remove quotes from type annotation
94+
UP037_0.py:26:33: UP037 [*] Remove quotes from type annotation
9595
|
9696
26 | def foo(*args: "str", **kwargs: "int"):
9797
| ^^^^^ UP037
@@ -109,7 +109,7 @@ UP037.py:26:33: UP037 [*] Remove quotes from type annotation
109109
28 28 |
110110
29 29 |
111111

112-
UP037.py:30:10: UP037 [*] Remove quotes from type annotation
112+
UP037_0.py:30:10: UP037 [*] Remove quotes from type annotation
113113
|
114114
30 | x: Tuple["MyClass"]
115115
| ^^^^^^^^^ UP037
@@ -128,7 +128,7 @@ UP037.py:30:10: UP037 [*] Remove quotes from type annotation
128128
32 32 | x: Callable[["MyClass"], None]
129129
33 33 |
130130

131-
UP037.py:32:14: UP037 [*] Remove quotes from type annotation
131+
UP037_0.py:32:14: UP037 [*] Remove quotes from type annotation
132132
|
133133
30 | x: Tuple["MyClass"]
134134
31 |
@@ -147,7 +147,7 @@ UP037.py:32:14: UP037 [*] Remove quotes from type annotation
147147
34 34 |
148148
35 35 | class Foo(NamedTuple):
149149

150-
UP037.py:36:8: UP037 [*] Remove quotes from type annotation
150+
UP037_0.py:36:8: UP037 [*] Remove quotes from type annotation
151151
|
152152
35 | class Foo(NamedTuple):
153153
36 | x: "MyClass"
@@ -165,7 +165,7 @@ UP037.py:36:8: UP037 [*] Remove quotes from type annotation
165165
38 38 |
166166
39 39 | class D(TypedDict):
167167

168-
UP037.py:40:27: UP037 [*] Remove quotes from type annotation
168+
UP037_0.py:40:27: UP037 [*] Remove quotes from type annotation
169169
|
170170
39 | class D(TypedDict):
171171
40 | E: TypedDict("E", foo="int", total=False)
@@ -183,7 +183,7 @@ UP037.py:40:27: UP037 [*] Remove quotes from type annotation
183183
42 42 |
184184
43 43 | class D(TypedDict):
185185

186-
UP037.py:44:31: UP037 [*] Remove quotes from type annotation
186+
UP037_0.py:44:31: UP037 [*] Remove quotes from type annotation
187187
|
188188
43 | class D(TypedDict):
189189
44 | E: TypedDict("E", {"foo": "int"})
@@ -201,7 +201,7 @@ UP037.py:44:31: UP037 [*] Remove quotes from type annotation
201201
46 46 |
202202
47 47 | x: Annotated["str", "metadata"]
203203

204-
UP037.py:47:14: UP037 [*] Remove quotes from type annotation
204+
UP037_0.py:47:14: UP037 [*] Remove quotes from type annotation
205205
|
206206
47 | x: Annotated["str", "metadata"]
207207
| ^^^^^ UP037
@@ -220,7 +220,7 @@ UP037.py:47:14: UP037 [*] Remove quotes from type annotation
220220
49 49 | x: Arg("str", "name")
221221
50 50 |
222222

223-
UP037.py:49:8: UP037 [*] Remove quotes from type annotation
223+
UP037_0.py:49:8: UP037 [*] Remove quotes from type annotation
224224
|
225225
47 | x: Annotated["str", "metadata"]
226226
48 |
@@ -241,7 +241,7 @@ UP037.py:49:8: UP037 [*] Remove quotes from type annotation
241241
51 51 | x: DefaultArg("str", "name")
242242
52 52 |
243243

244-
UP037.py:51:15: UP037 [*] Remove quotes from type annotation
244+
UP037_0.py:51:15: UP037 [*] Remove quotes from type annotation
245245
|
246246
49 | x: Arg("str", "name")
247247
50 |
@@ -262,7 +262,7 @@ UP037.py:51:15: UP037 [*] Remove quotes from type annotation
262262
53 53 | x: NamedArg("str", "name")
263263
54 54 |
264264

265-
UP037.py:53:13: UP037 [*] Remove quotes from type annotation
265+
UP037_0.py:53:13: UP037 [*] Remove quotes from type annotation
266266
|
267267
51 | x: DefaultArg("str", "name")
268268
52 |
@@ -283,7 +283,7 @@ UP037.py:53:13: UP037 [*] Remove quotes from type annotation
283283
55 55 | x: DefaultNamedArg("str", "name")
284284
56 56 |
285285

286-
UP037.py:55:20: UP037 [*] Remove quotes from type annotation
286+
UP037_0.py:55:20: UP037 [*] Remove quotes from type annotation
287287
|
288288
53 | x: NamedArg("str", "name")
289289
54 |
@@ -304,7 +304,7 @@ UP037.py:55:20: UP037 [*] Remove quotes from type annotation
304304
57 57 | x: DefaultNamedArg("str", name="name")
305305
58 58 |
306306

307-
UP037.py:57:20: UP037 [*] Remove quotes from type annotation
307+
UP037_0.py:57:20: UP037 [*] Remove quotes from type annotation
308308
|
309309
55 | x: DefaultNamedArg("str", "name")
310310
56 |
@@ -325,7 +325,7 @@ UP037.py:57:20: UP037 [*] Remove quotes from type annotation
325325
59 59 | x: VarArg("str")
326326
60 60 |
327327

328-
UP037.py:59:11: UP037 [*] Remove quotes from type annotation
328+
UP037_0.py:59:11: UP037 [*] Remove quotes from type annotation
329329
|
330330
57 | x: DefaultNamedArg("str", name="name")
331331
58 |
@@ -346,7 +346,7 @@ UP037.py:59:11: UP037 [*] Remove quotes from type annotation
346346
61 61 | x: List[List[List["MyClass"]]]
347347
62 62 |
348348

349-
UP037.py:61:19: UP037 [*] Remove quotes from type annotation
349+
UP037_0.py:61:19: UP037 [*] Remove quotes from type annotation
350350
|
351351
59 | x: VarArg("str")
352352
60 |
@@ -367,7 +367,7 @@ UP037.py:61:19: UP037 [*] Remove quotes from type annotation
367367
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
368368
64 64 |
369369

370-
UP037.py:63:29: UP037 [*] Remove quotes from type annotation
370+
UP037_0.py:63:29: UP037 [*] Remove quotes from type annotation
371371
|
372372
61 | x: List[List[List["MyClass"]]]
373373
62 |
@@ -388,7 +388,7 @@ UP037.py:63:29: UP037 [*] Remove quotes from type annotation
388388
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
389389
66 66 |
390390

391-
UP037.py:63:45: UP037 [*] Remove quotes from type annotation
391+
UP037_0.py:63:45: UP037 [*] Remove quotes from type annotation
392392
|
393393
61 | x: List[List[List["MyClass"]]]
394394
62 |
@@ -409,7 +409,7 @@ UP037.py:63:45: UP037 [*] Remove quotes from type annotation
409409
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
410410
66 66 |
411411

412-
UP037.py:65:29: UP037 [*] Remove quotes from type annotation
412+
UP037_0.py:65:29: UP037 [*] Remove quotes from type annotation
413413
|
414414
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
415415
64 |
@@ -430,7 +430,7 @@ UP037.py:65:29: UP037 [*] Remove quotes from type annotation
430430
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
431431
68 68 |
432432

433-
UP037.py:65:36: UP037 [*] Remove quotes from type annotation
433+
UP037_0.py:65:36: UP037 [*] Remove quotes from type annotation
434434
|
435435
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
436436
64 |
@@ -451,7 +451,7 @@ UP037.py:65:36: UP037 [*] Remove quotes from type annotation
451451
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
452452
68 68 |
453453

454-
UP037.py:65:45: UP037 [*] Remove quotes from type annotation
454+
UP037_0.py:65:45: UP037 [*] Remove quotes from type annotation
455455
|
456456
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
457457
64 |
@@ -472,7 +472,7 @@ UP037.py:65:45: UP037 [*] Remove quotes from type annotation
472472
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
473473
68 68 |
474474

475-
UP037.py:65:52: UP037 [*] Remove quotes from type annotation
475+
UP037_0.py:65:52: UP037 [*] Remove quotes from type annotation
476476
|
477477
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
478478
64 |
@@ -493,7 +493,7 @@ UP037.py:65:52: UP037 [*] Remove quotes from type annotation
493493
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
494494
68 68 |
495495

496-
UP037.py:67:24: UP037 [*] Remove quotes from type annotation
496+
UP037_0.py:67:24: UP037 [*] Remove quotes from type annotation
497497
|
498498
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
499499
66 |
@@ -514,7 +514,7 @@ UP037.py:67:24: UP037 [*] Remove quotes from type annotation
514514
69 69 | X: MyCallable("X")
515515
70 70 |
516516

517-
UP037.py:67:38: UP037 [*] Remove quotes from type annotation
517+
UP037_0.py:67:38: UP037 [*] Remove quotes from type annotation
518518
|
519519
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
520520
66 |
@@ -535,7 +535,7 @@ UP037.py:67:38: UP037 [*] Remove quotes from type annotation
535535
69 69 | X: MyCallable("X")
536536
70 70 |
537537

538-
UP037.py:67:45: UP037 [*] Remove quotes from type annotation
538+
UP037_0.py:67:45: UP037 [*] Remove quotes from type annotation
539539
|
540540
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
541541
66 |
@@ -554,6 +554,4 @@ UP037.py:67:45: UP037 [*] Remove quotes from type annotation
554554
67 |+x: NamedTuple(typename="X", fields=[("foo", int)])
555555
68 68 |
556556
69 69 | X: MyCallable("X")
557-
70 70 |
558-
559-
557+
70 70 |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
3+
---
4+
UP037_1.py:9:8: UP037 [*] Remove quotes from type annotation
5+
|
6+
7 | def foo():
7+
8 | # UP037
8+
9 | x: "Tuple[int, int]" = (0, 0)
9+
| ^^^^^^^^^^^^^^^^^ UP037
10+
10 | print(x)
11+
|
12+
= help: Remove quotes
13+
14+
Safe fix
15+
6 6 |
16+
7 7 | def foo():
17+
8 8 | # UP037
18+
9 |- x: "Tuple[int, int]" = (0, 0)
19+
9 |+ x: Tuple[int, int] = (0, 0)
20+
10 10 | print(x)
21+
11 11 |
22+
12 12 |

0 commit comments

Comments
 (0)