Skip to content

Commit b912dfc

Browse files
authored
[pyupgrade] Apply UP045 to string arguments of typing.cast (#22320)
## Summary Fixes #20096 by applying `UP045` to string type definitions as well as annotations. This is kind of a niche issue, especially now that Python 3.9 has reached end-of-life, but UP045 was not applying to string `Optional` arguments in `typing.cast` calls because these aren't annotations. This PR just augments the `in_annotation` check to `in_annotation || in_string_type_definition`. This means that `UP045` will also apply to other string type definitions like the rhs in: ```py from __future__ import annotations from typing_extensions import TypeAlias x: TypeAlias = "Optional[str]" ``` which I think is also okay. ## Test Plan New tests based on the issue
1 parent 1ff062d commit b912dfc

4 files changed

Lines changed: 136 additions & 1 deletion

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Regression test for https://github.com/astral-sh/ruff/issues/20096
3+
"""
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional, cast, TypeAlias
8+
9+
10+
x: Optional[str] # UP045
11+
x: "Optional[str]" # UP045
12+
cast("Optional[str]", None) # UP045
13+
cast(Optional[str], None) # okay, str | None is a runtime error
14+
x: TypeAlias = "Optional[str]" # UP045
15+
x: TypeAlias = Optional[str] # okay
16+
17+
# complex (implicitly concatenated) annotations
18+
#
19+
# TODO(brent) these aren't currently flagged by the rule but probably should
20+
# be. From what I can tell, this isn't an issue in the rule itself but rather
21+
# with our parsing of such annotations.
22+
x: (
23+
"Optional"
24+
"["
25+
"str"
26+
"]"
27+
)
28+
cast((
29+
"Optional"
30+
"["
31+
"str"
32+
"]"
33+
), None)
34+
x: TypeAlias = (
35+
"Optional"
36+
"["
37+
"str"
38+
"]"
39+
)

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
5454
|| checker.target_version() >= PythonVersion::PY310
5555
|| (checker.target_version() >= PythonVersion::PY37
5656
&& checker.semantic.future_annotations_or_stub()
57-
&& checker.semantic.in_annotation()
57+
&& (checker.semantic.in_annotation()
58+
|| checker.semantic.in_string_type_definition())
5859
&& !checker.settings().pyupgrade.keep_runtime_typing)
5960
{
6061
pyupgrade::rules::non_pep604_annotation(checker, expr, slice, operator);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,17 @@ mod tests {
453453
assert_diagnostics!(snapshot, diagnostics);
454454
Ok(())
455455
}
456+
457+
#[test]
458+
fn up045_future_annotations_py39() -> Result<()> {
459+
let diagnostics = test_path(
460+
Path::new("pyupgrade/UP045_py39.py"),
461+
&settings::LinterSettings {
462+
unresolved_target_version: PythonVersion::PY39.into(),
463+
..settings::LinterSettings::for_rule(Rule::NonPEP604AnnotationOptional)
464+
},
465+
)?;
466+
assert_diagnostics!(diagnostics);
467+
Ok(())
468+
}
456469
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
3+
---
4+
UP045 [*] Use `X | None` for type annotations
5+
--> UP045_py39.py:10:4
6+
|
7+
10 | x: Optional[str] # UP045
8+
| ^^^^^^^^^^^^^
9+
11 | x: "Optional[str]" # UP045
10+
12 | cast("Optional[str]", None) # UP045
11+
|
12+
help: Convert to `X | None`
13+
7 | from typing import Optional, cast, TypeAlias
14+
8 |
15+
9 |
16+
- x: Optional[str] # UP045
17+
10 + x: str | None # UP045
18+
11 | x: "Optional[str]" # UP045
19+
12 | cast("Optional[str]", None) # UP045
20+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
21+
note: This is an unsafe fix and may change runtime behavior
22+
23+
UP045 [*] Use `X | None` for type annotations
24+
--> UP045_py39.py:11:5
25+
|
26+
10 | x: Optional[str] # UP045
27+
11 | x: "Optional[str]" # UP045
28+
| ^^^^^^^^^^^^^
29+
12 | cast("Optional[str]", None) # UP045
30+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
31+
|
32+
help: Convert to `X | None`
33+
8 |
34+
9 |
35+
10 | x: Optional[str] # UP045
36+
- x: "Optional[str]" # UP045
37+
11 + x: "str | None" # UP045
38+
12 | cast("Optional[str]", None) # UP045
39+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
40+
14 | x: TypeAlias = "Optional[str]" # UP045
41+
note: This is an unsafe fix and may change runtime behavior
42+
43+
UP045 [*] Use `X | None` for type annotations
44+
--> UP045_py39.py:12:7
45+
|
46+
10 | x: Optional[str] # UP045
47+
11 | x: "Optional[str]" # UP045
48+
12 | cast("Optional[str]", None) # UP045
49+
| ^^^^^^^^^^^^^
50+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
51+
14 | x: TypeAlias = "Optional[str]" # UP045
52+
|
53+
help: Convert to `X | None`
54+
9 |
55+
10 | x: Optional[str] # UP045
56+
11 | x: "Optional[str]" # UP045
57+
- cast("Optional[str]", None) # UP045
58+
12 + cast("str | None", None) # UP045
59+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
60+
14 | x: TypeAlias = "Optional[str]" # UP045
61+
15 | x: TypeAlias = Optional[str] # okay
62+
note: This is an unsafe fix and may change runtime behavior
63+
64+
UP045 [*] Use `X | None` for type annotations
65+
--> UP045_py39.py:14:17
66+
|
67+
12 | cast("Optional[str]", None) # UP045
68+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
69+
14 | x: TypeAlias = "Optional[str]" # UP045
70+
| ^^^^^^^^^^^^^
71+
15 | x: TypeAlias = Optional[str] # okay
72+
|
73+
help: Convert to `X | None`
74+
11 | x: "Optional[str]" # UP045
75+
12 | cast("Optional[str]", None) # UP045
76+
13 | cast(Optional[str], None) # okay, str | None is a runtime error
77+
- x: TypeAlias = "Optional[str]" # UP045
78+
14 + x: TypeAlias = "str | None" # UP045
79+
15 | x: TypeAlias = Optional[str] # okay
80+
16 |
81+
17 | # complex (implicitly concatenated) annotations
82+
note: This is an unsafe fix and may change runtime behavior

0 commit comments

Comments
 (0)