Skip to content

Commit f4bd74a

Browse files
[ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333)
## Summary `ty` does not understand that calls to functions which have been annotated as having a return type of `Never` / `NoReturn` are terminal. This PR fixes that, by adding new reachability constraints when call expressions are seen. If the call expression evaluates to `Never`, the code following it will be considered to be unreachable. Note that, for adding these constraints, we only consider call expressions at the statement level, and that too only inside function scopes. This is because otherwise, the number of such constraints becomes too high, and evaluating them later on during type inference results in a major performance degradation. Fixes astral-sh/ty#180 ## Test Plan New mdtests. ## Ecosystem changes This PR removes the following false-positives: - "Function can implicitly return `None`, which is not assignable to ...". - "Name `foo` used when possibly not defind" - because the branch in which it is not defined has a `NoReturn` call, or when `foo` was imported in a `try`, and the except had a `NoReturn` call. --------- Co-authored-by: David Peter <mail@david-peter.de>
1 parent a33cff2 commit f4bd74a

File tree

10 files changed

+437
-72
lines changed

10 files changed

+437
-72
lines changed

crates/ty_python_semantic/resources/mdtest/directives/assert_never.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,53 @@
22

33
## Basic functionality
44

5-
<!-- snapshot-diagnostics -->
5+
`assert_never` makes sure that the type of the argument is `Never`.
66

7-
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
8-
`type-assertion-failure` diagnostic is emitted.
7+
### Correct usage
98

109
```py
1110
from typing_extensions import assert_never, Never, Any
1211
from ty_extensions import Unknown
1312

14-
def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
13+
def _(never: Never):
1514
assert_never(never) # fine
15+
```
16+
17+
### Diagnostics
18+
19+
<!-- snapshot-diagnostics -->
1620

21+
If it is not, a `type-assertion-failure` diagnostic is emitted.
22+
23+
```py
24+
from typing_extensions import assert_never, Never, Any
25+
from ty_extensions import Unknown
26+
27+
def _():
1728
assert_never(0) # error: [type-assertion-failure]
29+
30+
def _():
1831
assert_never("") # error: [type-assertion-failure]
32+
33+
def _():
1934
assert_never(None) # error: [type-assertion-failure]
35+
36+
def _():
2037
assert_never([]) # error: [type-assertion-failure]
38+
39+
def _():
2140
assert_never({}) # error: [type-assertion-failure]
41+
42+
def _():
2243
assert_never(()) # error: [type-assertion-failure]
44+
45+
def _(flag: bool, never: Never):
2346
assert_never(1 if flag else never) # error: [type-assertion-failure]
2447

48+
def _(any_: Any):
2549
assert_never(any_) # error: [type-assertion-failure]
50+
51+
def _(unknown: Unknown):
2652
assert_never(unknown) # error: [type-assertion-failure]
2753
```
2854

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
33
expression: snapshot
44
---
55
---
6-
mdtest name: assert_never.md - `assert_never` - Basic functionality
6+
mdtest name: assert_never.md - `assert_never` - Basic functionality - Diagnostics
77
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
88
---
99

@@ -15,35 +15,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.
1515
1 | from typing_extensions import assert_never, Never, Any
1616
2 | from ty_extensions import Unknown
1717
3 |
18-
4 | def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
19-
5 | assert_never(never) # fine
18+
4 | def _():
19+
5 | assert_never(0) # error: [type-assertion-failure]
2020
6 |
21-
7 | assert_never(0) # error: [type-assertion-failure]
21+
7 | def _():
2222
8 | assert_never("") # error: [type-assertion-failure]
23-
9 | assert_never(None) # error: [type-assertion-failure]
24-
10 | assert_never([]) # error: [type-assertion-failure]
25-
11 | assert_never({}) # error: [type-assertion-failure]
26-
12 | assert_never(()) # error: [type-assertion-failure]
27-
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
28-
14 |
29-
15 | assert_never(any_) # error: [type-assertion-failure]
30-
16 | assert_never(unknown) # error: [type-assertion-failure]
23+
9 |
24+
10 | def _():
25+
11 | assert_never(None) # error: [type-assertion-failure]
26+
12 |
27+
13 | def _():
28+
14 | assert_never([]) # error: [type-assertion-failure]
29+
15 |
30+
16 | def _():
31+
17 | assert_never({}) # error: [type-assertion-failure]
32+
18 |
33+
19 | def _():
34+
20 | assert_never(()) # error: [type-assertion-failure]
35+
21 |
36+
22 | def _(flag: bool, never: Never):
37+
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
38+
24 |
39+
25 | def _(any_: Any):
40+
26 | assert_never(any_) # error: [type-assertion-failure]
41+
27 |
42+
28 | def _(unknown: Unknown):
43+
29 | assert_never(unknown) # error: [type-assertion-failure]
3144
```
3245

3346
# Diagnostics
3447

3548
```
3649
error[type-assertion-failure]: Argument does not have asserted type `Never`
37-
--> src/mdtest_snippet.py:7:5
50+
--> src/mdtest_snippet.py:5:5
3851
|
39-
5 | assert_never(never) # fine
40-
6 |
41-
7 | assert_never(0) # error: [type-assertion-failure]
52+
4 | def _():
53+
5 | assert_never(0) # error: [type-assertion-failure]
4254
| ^^^^^^^^^^^^^-^
4355
| |
4456
| Inferred type of argument is `Literal[0]`
45-
8 | assert_never("") # error: [type-assertion-failure]
46-
9 | assert_never(None) # error: [type-assertion-failure]
57+
6 |
58+
7 | def _():
4759
|
4860
info: `Never` and `Literal[0]` are not equivalent types
4961
info: rule `type-assertion-failure` is enabled by default
@@ -54,13 +66,13 @@ info: rule `type-assertion-failure` is enabled by default
5466
error[type-assertion-failure]: Argument does not have asserted type `Never`
5567
--> src/mdtest_snippet.py:8:5
5668
|
57-
7 | assert_never(0) # error: [type-assertion-failure]
69+
7 | def _():
5870
8 | assert_never("") # error: [type-assertion-failure]
5971
| ^^^^^^^^^^^^^--^
6072
| |
6173
| Inferred type of argument is `Literal[""]`
62-
9 | assert_never(None) # error: [type-assertion-failure]
63-
10 | assert_never([]) # error: [type-assertion-failure]
74+
9 |
75+
10 | def _():
6476
|
6577
info: `Never` and `Literal[""]` are not equivalent types
6678
info: rule `type-assertion-failure` is enabled by default
@@ -69,16 +81,15 @@ info: rule `type-assertion-failure` is enabled by default
6981

7082
```
7183
error[type-assertion-failure]: Argument does not have asserted type `Never`
72-
--> src/mdtest_snippet.py:9:5
84+
--> src/mdtest_snippet.py:11:5
7385
|
74-
7 | assert_never(0) # error: [type-assertion-failure]
75-
8 | assert_never("") # error: [type-assertion-failure]
76-
9 | assert_never(None) # error: [type-assertion-failure]
86+
10 | def _():
87+
11 | assert_never(None) # error: [type-assertion-failure]
7788
| ^^^^^^^^^^^^^----^
7889
| |
7990
| Inferred type of argument is `None`
80-
10 | assert_never([]) # error: [type-assertion-failure]
81-
11 | assert_never({}) # error: [type-assertion-failure]
91+
12 |
92+
13 | def _():
8293
|
8394
info: `Never` and `None` are not equivalent types
8495
info: rule `type-assertion-failure` is enabled by default
@@ -87,16 +98,15 @@ info: rule `type-assertion-failure` is enabled by default
8798

8899
```
89100
error[type-assertion-failure]: Argument does not have asserted type `Never`
90-
--> src/mdtest_snippet.py:10:5
101+
--> src/mdtest_snippet.py:14:5
91102
|
92-
8 | assert_never("") # error: [type-assertion-failure]
93-
9 | assert_never(None) # error: [type-assertion-failure]
94-
10 | assert_never([]) # error: [type-assertion-failure]
103+
13 | def _():
104+
14 | assert_never([]) # error: [type-assertion-failure]
95105
| ^^^^^^^^^^^^^--^
96106
| |
97107
| Inferred type of argument is `list[Unknown]`
98-
11 | assert_never({}) # error: [type-assertion-failure]
99-
12 | assert_never(()) # error: [type-assertion-failure]
108+
15 |
109+
16 | def _():
100110
|
101111
info: `Never` and `list[Unknown]` are not equivalent types
102112
info: rule `type-assertion-failure` is enabled by default
@@ -105,16 +115,15 @@ info: rule `type-assertion-failure` is enabled by default
105115

106116
```
107117
error[type-assertion-failure]: Argument does not have asserted type `Never`
108-
--> src/mdtest_snippet.py:11:5
118+
--> src/mdtest_snippet.py:17:5
109119
|
110-
9 | assert_never(None) # error: [type-assertion-failure]
111-
10 | assert_never([]) # error: [type-assertion-failure]
112-
11 | assert_never({}) # error: [type-assertion-failure]
120+
16 | def _():
121+
17 | assert_never({}) # error: [type-assertion-failure]
113122
| ^^^^^^^^^^^^^--^
114123
| |
115124
| Inferred type of argument is `dict[Unknown, Unknown]`
116-
12 | assert_never(()) # error: [type-assertion-failure]
117-
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
125+
18 |
126+
19 | def _():
118127
|
119128
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
120129
info: rule `type-assertion-failure` is enabled by default
@@ -123,15 +132,15 @@ info: rule `type-assertion-failure` is enabled by default
123132

124133
```
125134
error[type-assertion-failure]: Argument does not have asserted type `Never`
126-
--> src/mdtest_snippet.py:12:5
135+
--> src/mdtest_snippet.py:20:5
127136
|
128-
10 | assert_never([]) # error: [type-assertion-failure]
129-
11 | assert_never({}) # error: [type-assertion-failure]
130-
12 | assert_never(()) # error: [type-assertion-failure]
137+
19 | def _():
138+
20 | assert_never(()) # error: [type-assertion-failure]
131139
| ^^^^^^^^^^^^^--^
132140
| |
133141
| Inferred type of argument is `tuple[()]`
134-
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
142+
21 |
143+
22 | def _(flag: bool, never: Never):
135144
|
136145
info: `Never` and `tuple[()]` are not equivalent types
137146
info: rule `type-assertion-failure` is enabled by default
@@ -140,16 +149,15 @@ info: rule `type-assertion-failure` is enabled by default
140149

141150
```
142151
error[type-assertion-failure]: Argument does not have asserted type `Never`
143-
--> src/mdtest_snippet.py:13:5
152+
--> src/mdtest_snippet.py:23:5
144153
|
145-
11 | assert_never({}) # error: [type-assertion-failure]
146-
12 | assert_never(()) # error: [type-assertion-failure]
147-
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
154+
22 | def _(flag: bool, never: Never):
155+
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
148156
| ^^^^^^^^^^^^^--------------------^
149157
| |
150158
| Inferred type of argument is `Literal[1]`
151-
14 |
152-
15 | assert_never(any_) # error: [type-assertion-failure]
159+
24 |
160+
25 | def _(any_: Any):
153161
|
154162
info: `Never` and `Literal[1]` are not equivalent types
155163
info: rule `type-assertion-failure` is enabled by default
@@ -158,15 +166,15 @@ info: rule `type-assertion-failure` is enabled by default
158166

159167
```
160168
error[type-assertion-failure]: Argument does not have asserted type `Never`
161-
--> src/mdtest_snippet.py:15:5
169+
--> src/mdtest_snippet.py:26:5
162170
|
163-
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
164-
14 |
165-
15 | assert_never(any_) # error: [type-assertion-failure]
171+
25 | def _(any_: Any):
172+
26 | assert_never(any_) # error: [type-assertion-failure]
166173
| ^^^^^^^^^^^^^----^
167174
| |
168175
| Inferred type of argument is `Any`
169-
16 | assert_never(unknown) # error: [type-assertion-failure]
176+
27 |
177+
28 | def _(unknown: Unknown):
170178
|
171179
info: `Never` and `Any` are not equivalent types
172180
info: rule `type-assertion-failure` is enabled by default
@@ -175,10 +183,10 @@ info: rule `type-assertion-failure` is enabled by default
175183

176184
```
177185
error[type-assertion-failure]: Argument does not have asserted type `Never`
178-
--> src/mdtest_snippet.py:16:5
186+
--> src/mdtest_snippet.py:29:5
179187
|
180-
15 | assert_never(any_) # error: [type-assertion-failure]
181-
16 | assert_never(unknown) # error: [type-assertion-failure]
188+
28 | def _(unknown: Unknown):
189+
29 | assert_never(unknown) # error: [type-assertion-failure]
182190
| ^^^^^^^^^^^^^-------^
183191
| |
184192
| Inferred type of argument is `Unknown`

0 commit comments

Comments
 (0)