Skip to content

Commit

Permalink
Mark quotes as unnecessary for non-evaluated annotations (#11485)
Browse files Browse the repository at this point in the history
## 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()
```
  • Loading branch information
charliermarsh authored May 22, 2024
1 parent 573facd commit 519a650
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 34 deletions.
2 changes: 1 addition & 1 deletion crates/ruff/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,7 @@ fn check_input_from_argfile() -> Result<()> {
fs::write(&file_a_path, b"import os")?;
fs::write(&file_b_path, b"print('hello, world!')")?;

// Create a the input file for argfile to expand
// Create the input file for argfile to expand
let input_file_path = tempdir.path().join("file_paths.txt");
fs::write(
&input_file_path,
Expand Down
14 changes: 14 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Tuple


def foo():
# UP037
x: "Tuple[int, int]" = (0, 0)
print(x)


# OK
X: "Tuple[int, int]" = (0, 0)
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2152,7 +2152,7 @@ impl<'a> Checker<'a> {

self.semantic.restore(snapshot);

if self.semantic.in_annotation() && self.semantic.future_annotations_or_stub() {
if self.semantic.in_annotation() && self.semantic.in_typing_only_annotation() {
if self.enabled(Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, value, range);
}
Expand Down
3 changes: 2 additions & 1 deletion crates/ruff_linter/src/rules/pyupgrade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ mod tests {
#[test_case(Rule::OutdatedVersionBlock, Path::new("UP036_5.py"))]
#[test_case(Rule::PrintfStringFormatting, Path::new("UP031_0.py"))]
#[test_case(Rule::PrintfStringFormatting, Path::new("UP031_1.py"))]
#[test_case(Rule::QuotedAnnotation, Path::new("UP037.py"))]
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_0.py"))]
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_1.py"))]
#[test_case(Rule::RedundantOpenModes, Path::new("UP015.py"))]
#[test_case(Rule::ReplaceStdoutStderr, Path::new("UP022.py"))]
#[test_case(Rule::ReplaceUniversalNewlines, Path::new("UP021.py"))]
Expand Down
19 changes: 19 additions & 0 deletions crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ use crate::checkers::ast::Checker;
///
/// ## Why is this bad?
/// In Python, type annotations can be quoted to avoid forward references.
///
/// However, if `from __future__ import annotations` is present, Python
/// will always evaluate type annotations in a deferred manner, making
/// the quotes unnecessary.
///
/// Similarly, if the annotation is located in a typing-only context and
/// won't be evaluated by Python at runtime, the quotes will also be
/// considered unnecessary. For example, Python does not evaluate type
/// annotations on assignments in function bodies.
///
/// ## Example
/// Given:
/// ```python
/// from __future__ import annotations
///
Expand All @@ -32,6 +39,18 @@ use crate::checkers::ast::Checker;
/// ...
/// ```
///
/// Given:
/// ```python
/// def foo() -> None:
/// bar: "Bar"
/// ```
///
/// Use instead:
/// ```python
/// def foo() -> None:
/// bar: Bar
/// ```
///
/// ## References
/// - [PEP 563](https://peps.python.org/pep-0563/)
/// - [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP037.py:18:14: UP037 [*] Remove quotes from type annotation
UP037_0.py:18:14: UP037 [*] Remove quotes from type annotation
|
18 | def foo(var: "MyClass") -> "MyClass":
| ^^^^^^^^^ UP037
Expand All @@ -19,7 +19,7 @@ UP037.py:18:14: UP037 [*] Remove quotes from type annotation
20 20 |
21 21 |

UP037.py:18:28: UP037 [*] Remove quotes from type annotation
UP037_0.py:18:28: UP037 [*] Remove quotes from type annotation
|
18 | def foo(var: "MyClass") -> "MyClass":
| ^^^^^^^^^ UP037
Expand All @@ -37,7 +37,7 @@ UP037.py:18:28: UP037 [*] Remove quotes from type annotation
20 20 |
21 21 |

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

UP037.py:22:21: UP037 [*] Remove quotes from type annotation
UP037_0.py:22:21: UP037 [*] Remove quotes from type annotation
|
22 | def foo(*, inplace: "bool"):
| ^^^^^^ UP037
Expand All @@ -73,7 +73,7 @@ UP037.py:22:21: UP037 [*] Remove quotes from type annotation
24 24 |
25 25 |

UP037.py:26:16: UP037 [*] Remove quotes from type annotation
UP037_0.py:26:16: UP037 [*] Remove quotes from type annotation
|
26 | def foo(*args: "str", **kwargs: "int"):
| ^^^^^ UP037
Expand All @@ -91,7 +91,7 @@ UP037.py:26:16: UP037 [*] Remove quotes from type annotation
28 28 |
29 29 |

UP037.py:26:33: UP037 [*] Remove quotes from type annotation
UP037_0.py:26:33: UP037 [*] Remove quotes from type annotation
|
26 | def foo(*args: "str", **kwargs: "int"):
| ^^^^^ UP037
Expand All @@ -109,7 +109,7 @@ UP037.py:26:33: UP037 [*] Remove quotes from type annotation
28 28 |
29 29 |

UP037.py:30:10: UP037 [*] Remove quotes from type annotation
UP037_0.py:30:10: UP037 [*] Remove quotes from type annotation
|
30 | x: Tuple["MyClass"]
| ^^^^^^^^^ UP037
Expand All @@ -128,7 +128,7 @@ UP037.py:30:10: UP037 [*] Remove quotes from type annotation
32 32 | x: Callable[["MyClass"], None]
33 33 |

UP037.py:32:14: UP037 [*] Remove quotes from type annotation
UP037_0.py:32:14: UP037 [*] Remove quotes from type annotation
|
30 | x: Tuple["MyClass"]
31 |
Expand All @@ -147,7 +147,7 @@ UP037.py:32:14: UP037 [*] Remove quotes from type annotation
34 34 |
35 35 | class Foo(NamedTuple):

UP037.py:36:8: UP037 [*] Remove quotes from type annotation
UP037_0.py:36:8: UP037 [*] Remove quotes from type annotation
|
35 | class Foo(NamedTuple):
36 | x: "MyClass"
Expand All @@ -165,7 +165,7 @@ UP037.py:36:8: UP037 [*] Remove quotes from type annotation
38 38 |
39 39 | class D(TypedDict):

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

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

UP037.py:47:14: UP037 [*] Remove quotes from type annotation
UP037_0.py:47:14: UP037 [*] Remove quotes from type annotation
|
47 | x: Annotated["str", "metadata"]
| ^^^^^ UP037
Expand All @@ -220,7 +220,7 @@ UP037.py:47:14: UP037 [*] Remove quotes from type annotation
49 49 | x: Arg("str", "name")
50 50 |

UP037.py:49:8: UP037 [*] Remove quotes from type annotation
UP037_0.py:49:8: UP037 [*] Remove quotes from type annotation
|
47 | x: Annotated["str", "metadata"]
48 |
Expand All @@ -241,7 +241,7 @@ UP037.py:49:8: UP037 [*] Remove quotes from type annotation
51 51 | x: DefaultArg("str", "name")
52 52 |

UP037.py:51:15: UP037 [*] Remove quotes from type annotation
UP037_0.py:51:15: UP037 [*] Remove quotes from type annotation
|
49 | x: Arg("str", "name")
50 |
Expand All @@ -262,7 +262,7 @@ UP037.py:51:15: UP037 [*] Remove quotes from type annotation
53 53 | x: NamedArg("str", "name")
54 54 |

UP037.py:53:13: UP037 [*] Remove quotes from type annotation
UP037_0.py:53:13: UP037 [*] Remove quotes from type annotation
|
51 | x: DefaultArg("str", "name")
52 |
Expand All @@ -283,7 +283,7 @@ UP037.py:53:13: UP037 [*] Remove quotes from type annotation
55 55 | x: DefaultNamedArg("str", "name")
56 56 |

UP037.py:55:20: UP037 [*] Remove quotes from type annotation
UP037_0.py:55:20: UP037 [*] Remove quotes from type annotation
|
53 | x: NamedArg("str", "name")
54 |
Expand All @@ -304,7 +304,7 @@ UP037.py:55:20: UP037 [*] Remove quotes from type annotation
57 57 | x: DefaultNamedArg("str", name="name")
58 58 |

UP037.py:57:20: UP037 [*] Remove quotes from type annotation
UP037_0.py:57:20: UP037 [*] Remove quotes from type annotation
|
55 | x: DefaultNamedArg("str", "name")
56 |
Expand All @@ -325,7 +325,7 @@ UP037.py:57:20: UP037 [*] Remove quotes from type annotation
59 59 | x: VarArg("str")
60 60 |

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

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

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

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

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

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

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

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

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

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

UP037.py:67:45: UP037 [*] Remove quotes from type annotation
UP037_0.py:67:45: UP037 [*] Remove quotes from type annotation
|
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 |
Expand All @@ -554,6 +554,4 @@ UP037.py:67:45: UP037 [*] Remove quotes from type annotation
67 |+x: NamedTuple(typename="X", fields=[("foo", int)])
68 68 |
69 69 | X: MyCallable("X")
70 70 |


70 70 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP037_1.py:9:8: UP037 [*] Remove quotes from type annotation
|
7 | def foo():
8 | # UP037
9 | x: "Tuple[int, int]" = (0, 0)
| ^^^^^^^^^^^^^^^^^ UP037
10 | print(x)
|
= help: Remove quotes

Safe fix
6 6 |
7 7 | def foo():
8 8 | # UP037
9 |- x: "Tuple[int, int]" = (0, 0)
9 |+ x: Tuple[int, int] = (0, 0)
10 10 | print(x)
11 11 |
12 12 |

0 comments on commit 519a650

Please sign in to comment.