Skip to content

Commit 91fc7bd

Browse files
authored
[ty] Fix false-positive diagnostics for PEP-604 union annotations on attribute targets on Python 3.9 when from __future__ import annotations is active (#23915)
1 parent 04229cf commit 91fc7bd

4 files changed

Lines changed: 148 additions & 3 deletions

File tree

crates/ty_python_semantic/resources/mdtest/annotations/string.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,12 @@ from __future__ import annotations
170170
def f(v: int | "Foo"): # fine
171171
reveal_type(v) # revealed: int | Foo
172172

173-
class Foo: ...
173+
class Foo:
174+
def __init__(self):
175+
self.x: "int" | "str" = 42
176+
177+
d = {}
178+
d[0]: "int" | "str" = 42
174179

175180
# error: [unsupported-operator]
176181
X = list["int" | None]

crates/ty_python_semantic/resources/mdtest/annotations/union.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,45 @@ X = int | str
8383
def f(y: X):
8484
reveal_type(y) # revealed: int | str
8585
```
86+
87+
## Diagnostics for PEP-604 unions used on Python less than 3.10
88+
89+
<!-- snapshot-diagnostics -->
90+
91+
PEP-604 unions generally don't work on Python \<=3.9:
92+
93+
```toml
94+
[environment]
95+
python-version = "3.9"
96+
```
97+
98+
`a.py`:
99+
100+
```py
101+
x: int | str # error: [unsupported-operator]
102+
103+
class Foo:
104+
def __init__(self):
105+
self.x: int | str = 42 # error: [unsupported-operator]
106+
107+
d = {}
108+
d[0]: int | str = 42 # error: [unsupported-operator]
109+
```
110+
111+
But these runtime errors can be avoided if you add `from __future__ import annotations` to the top
112+
of your file:
113+
114+
`b.py`:
115+
116+
```py
117+
from __future__ import annotations
118+
119+
x: int | str
120+
121+
class Foo:
122+
def __init__(self):
123+
self.x: int | str = 42
124+
125+
d = {}
126+
d[0]: int | str = 42
127+
```
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
source: crates/ty_test/src/lib.rs
3+
expression: snapshot
4+
---
5+
6+
---
7+
mdtest name: union.md - Union - Diagnostics for PEP-604 unions used on Python less than 3.10
8+
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/union.md
9+
---
10+
11+
# Python source files
12+
13+
## a.py
14+
15+
```
16+
1 | x: int | str # error: [unsupported-operator]
17+
2 |
18+
3 | class Foo:
19+
4 | def __init__(self):
20+
5 | self.x: int | str = 42 # error: [unsupported-operator]
21+
6 |
22+
7 | d = {}
23+
8 | d[0]: int | str = 42 # error: [unsupported-operator]
24+
```
25+
26+
## b.py
27+
28+
```
29+
1 | from __future__ import annotations
30+
2 |
31+
3 | x: int | str
32+
4 |
33+
5 | class Foo:
34+
6 | def __init__(self):
35+
7 | self.x: int | str = 42
36+
8 |
37+
9 | d = {}
38+
10 | d[0]: int | str = 42
39+
```
40+
41+
# Diagnostics
42+
43+
```
44+
error[unsupported-operator]: Unsupported `|` operation
45+
--> src/a.py:1:4
46+
|
47+
1 | x: int | str # error: [unsupported-operator]
48+
| ---^^^---
49+
| | |
50+
| | Has type `<class 'str'>`
51+
| Has type `<class 'int'>`
52+
2 |
53+
3 | class Foo:
54+
|
55+
info: PEP 604 `|` unions are only available on Python 3.10+ unless they are quoted
56+
info: Python 3.9 was assumed when inferring types because it was specified on the command line
57+
info: rule `unsupported-operator` is enabled by default
58+
59+
```
60+
61+
```
62+
error[unsupported-operator]: Unsupported `|` operation
63+
--> src/a.py:5:17
64+
|
65+
3 | class Foo:
66+
4 | def __init__(self):
67+
5 | self.x: int | str = 42 # error: [unsupported-operator]
68+
| ---^^^---
69+
| | |
70+
| | Has type `<class 'str'>`
71+
| Has type `<class 'int'>`
72+
6 |
73+
7 | d = {}
74+
|
75+
info: PEP 604 `|` unions are only available on Python 3.10+ unless they are quoted
76+
info: Python 3.9 was assumed when inferring types because it was specified on the command line
77+
info: rule `unsupported-operator` is enabled by default
78+
79+
```
80+
81+
```
82+
error[unsupported-operator]: Unsupported `|` operation
83+
--> src/a.py:8:7
84+
|
85+
7 | d = {}
86+
8 | d[0]: int | str = 42 # error: [unsupported-operator]
87+
| ---^^^---
88+
| | |
89+
| | Has type `<class 'str'>`
90+
| Has type `<class 'int'>`
91+
|
92+
info: PEP 604 `|` unions are only available on Python 3.10+ unless they are quoted
93+
info: Python 3.9 was assumed when inferring types because it was specified on the command line
94+
info: rule `unsupported-operator` is enabled by default
95+
96+
```

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3780,8 +3780,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
37803780
target,
37813781
simple: _,
37823782
} = assignment;
3783-
let annotated =
3784-
self.infer_annotation_expression(annotation, DeferredExpressionState::None);
3783+
let annotated = self.infer_annotation_expression(
3784+
annotation,
3785+
DeferredExpressionState::from(self.defer_annotations()),
3786+
);
37853787

37863788
if !annotated.qualifiers.is_empty() {
37873789
for qualifier in [TypeQualifiers::CLASS_VAR, TypeQualifiers::INIT_VAR] {

0 commit comments

Comments
 (0)