Skip to content

Commit 0752d0b

Browse files
draft never handling
1 parent 8d5655a commit 0752d0b

File tree

9 files changed

+329
-8
lines changed

9 files changed

+329
-8
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
---
2+
source: crates/ty_test/src/lib.rs
3+
expression: snapshot
4+
---
5+
---
6+
mdtest name: assert_never.md - `assert_never` - Basic functionality - Diagnostics
7+
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
8+
---
9+
10+
# Python source files
11+
12+
## mdtest_snippet.py
13+
14+
```
15+
1 | from typing_extensions import assert_never, Never, Any
16+
2 | from ty_extensions import Unknown
17+
3 |
18+
4 | def _():
19+
5 | assert_never(0) # error: [type-assertion-failure]
20+
6 |
21+
7 | def _():
22+
8 | assert_never("") # 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]
44+
```
45+
46+
# Diagnostics
47+
48+
```
49+
error[type-assertion-failure]: Argument does not have asserted type `Never`
50+
--> src/mdtest_snippet.py:5:5
51+
|
52+
4 | def _():
53+
5 | assert_never(0) # error: [type-assertion-failure]
54+
| ^^^^^^^^^^^^^-^
55+
| |
56+
| Inferred type of argument is `Literal[0]`
57+
6 |
58+
7 | def _():
59+
|
60+
info: `Never` and `Literal[0]` are not equivalent types
61+
info: rule `type-assertion-failure` is enabled by default
62+
63+
```
64+
65+
```
66+
error[type-assertion-failure]: Argument does not have asserted type `Never`
67+
--> src/mdtest_snippet.py:8:5
68+
|
69+
7 | def _():
70+
8 | assert_never("") # error: [type-assertion-failure]
71+
| ^^^^^^^^^^^^^--^
72+
| |
73+
| Inferred type of argument is `Literal[""]`
74+
9 |
75+
10 | def _():
76+
|
77+
info: `Never` and `Literal[""]` are not equivalent types
78+
info: rule `type-assertion-failure` is enabled by default
79+
80+
```
81+
82+
```
83+
error[type-assertion-failure]: Argument does not have asserted type `Never`
84+
--> src/mdtest_snippet.py:11:5
85+
|
86+
10 | def _():
87+
11 | assert_never(None) # error: [type-assertion-failure]
88+
| ^^^^^^^^^^^^^----^
89+
| |
90+
| Inferred type of argument is `None`
91+
12 |
92+
13 | def _():
93+
|
94+
info: `Never` and `None` are not equivalent types
95+
info: rule `type-assertion-failure` is enabled by default
96+
97+
```
98+
99+
```
100+
error[type-assertion-failure]: Argument does not have asserted type `Never`
101+
--> src/mdtest_snippet.py:14:5
102+
|
103+
13 | def _():
104+
14 | assert_never([]) # error: [type-assertion-failure]
105+
| ^^^^^^^^^^^^^--^
106+
| |
107+
| Inferred type of argument is `list[Unknown]`
108+
15 |
109+
16 | def _():
110+
|
111+
info: `Never` and `list[Unknown]` are not equivalent types
112+
info: rule `type-assertion-failure` is enabled by default
113+
114+
```
115+
116+
```
117+
error[type-assertion-failure]: Argument does not have asserted type `Never`
118+
--> src/mdtest_snippet.py:17:5
119+
|
120+
16 | def _():
121+
17 | assert_never({}) # error: [type-assertion-failure]
122+
| ^^^^^^^^^^^^^--^
123+
| |
124+
| Inferred type of argument is `dict[Unknown, Unknown]`
125+
18 |
126+
19 | def _():
127+
|
128+
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
129+
info: rule `type-assertion-failure` is enabled by default
130+
131+
```
132+
133+
```
134+
error[type-assertion-failure]: Argument does not have asserted type `Never`
135+
--> src/mdtest_snippet.py:20:5
136+
|
137+
19 | def _():
138+
20 | assert_never(()) # error: [type-assertion-failure]
139+
| ^^^^^^^^^^^^^--^
140+
| |
141+
| Inferred type of argument is `tuple[()]`
142+
21 |
143+
22 | def _(flag: bool, never: Never):
144+
|
145+
info: `Never` and `tuple[()]` are not equivalent types
146+
info: rule `type-assertion-failure` is enabled by default
147+
148+
```
149+
150+
```
151+
error[type-assertion-failure]: Argument does not have asserted type `Never`
152+
--> src/mdtest_snippet.py:23:5
153+
|
154+
22 | def _(flag: bool, never: Never):
155+
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
156+
| ^^^^^^^^^^^^^--------------------^
157+
| |
158+
| Inferred type of argument is `Literal[1]`
159+
24 |
160+
25 | def _(any_: Any):
161+
|
162+
info: `Never` and `Literal[1]` are not equivalent types
163+
info: rule `type-assertion-failure` is enabled by default
164+
165+
```
166+
167+
```
168+
error[type-assertion-failure]: Argument does not have asserted type `Never`
169+
--> src/mdtest_snippet.py:26:5
170+
|
171+
25 | def _(any_: Any):
172+
26 | assert_never(any_) # error: [type-assertion-failure]
173+
| ^^^^^^^^^^^^^----^
174+
| |
175+
| Inferred type of argument is `Any`
176+
27 |
177+
28 | def _(unknown: Unknown):
178+
|
179+
info: `Never` and `Any` are not equivalent types
180+
info: rule `type-assertion-failure` is enabled by default
181+
182+
```
183+
184+
```
185+
error[type-assertion-failure]: Argument does not have asserted type `Never`
186+
--> src/mdtest_snippet.py:29:5
187+
|
188+
28 | def _(unknown: Unknown):
189+
29 | assert_never(unknown) # error: [type-assertion-failure]
190+
| ^^^^^^^^^^^^^-------^
191+
| |
192+
| Inferred type of argument is `Unknown`
193+
|
194+
info: `Never` and `Unknown` are not equivalent types
195+
info: rule `type-assertion-failure` is enabled by default
196+
197+
```

crates/ty_python_semantic/resources/mdtest/terminal_statements.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,37 @@ def f():
570570
reveal_type(x) # revealed: Literal[1]
571571
```
572572

573+
## Calls to functions returning `Never`
574+
575+
Calls to functions which have an annotated return type of `Never` are terminal.
576+
577+
```py
578+
from typing import NoReturn
579+
import sys
580+
581+
def f() -> NoReturn:
582+
sys.exit(1)
583+
584+
def g(x: int | None):
585+
if x is None:
586+
sys.exit(1)
587+
588+
# TODO: should be just int, not int | None
589+
reveal_type(x) # revealed: int | None
590+
```
591+
592+
Bindings after terminal statement is unreachable:
593+
594+
```py
595+
def _():
596+
x = 3
597+
598+
sys.exit(1)
599+
600+
x = 4
601+
reveal_type(x) # revealed: Never
602+
```
603+
573604
## Nested functions
574605

575606
Free references inside of a function body refer to variables defined in the containing scope.

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,11 +1949,29 @@ where
19491949
}
19501950
walk_stmt(self, stmt);
19511951
}
1952-
ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) if self.in_module_scope() => {
1953-
if let Some(expr) = dunder_all_extend_argument(value) {
1954-
self.add_standalone_expression(expr);
1952+
ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) => {
1953+
if self.in_module_scope() {
1954+
if let Some(expr) = dunder_all_extend_argument(value) {
1955+
self.add_standalone_expression(expr);
1956+
}
19551957
}
1958+
19561959
self.visit_expr(value);
1960+
1961+
// HACK: for now, only consider function calls which are top level expressions.
1962+
// This should also be done for calls in sub-expressions, example:
1963+
// `3 + f()`
1964+
if let Some(ast::ExprCall { func, .. }) = value.as_call_expr() {
1965+
let expression = self.add_standalone_expression(func);
1966+
1967+
let predicate = Predicate {
1968+
node: PredicateNode::ReturnsNever(expression),
1969+
is_positive: false,
1970+
};
1971+
1972+
self.record_reachability_constraint(predicate);
1973+
self.record_visibility_constraint(predicate);
1974+
}
19571975
}
19581976
_ => {
19591977
walk_stmt(self, stmt);

crates/ty_python_semantic/src/semantic_index/predicate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl Predicate<'_> {
6161
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
6262
pub(crate) enum PredicateNode<'db> {
6363
Expression(Expression<'db>),
64+
ReturnsNever(Expression<'db>),
6465
Pattern(PatternPredicate<'db>),
6566
StarImportPlaceholder(StarImportPlaceholderPredicate<'db>),
6667
}

crates/ty_python_semantic/src/semantic_index/use_def.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@
184184
//! end of the scope, it records the state for each symbol as the public definitions of that
185185
//! symbol.
186186
//!
187+
//! ```python
188+
//! x = 1
189+
//! x = 2
190+
//! y = x
191+
//! if flag:
192+
//! x = 3
193+
//! else:
194+
//! x = 4
195+
//! z = x
196+
//! ```
197+
//!
187198
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
188199
//! the new symbol (before we process the first binding), we create a new undefined `SymbolState`
189200
//! which has a single live binding (the "unbound" definition) and a single live declaration (the

crates/ty_python_semantic/src/semantic_index/visibility_constraints.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,34 @@ impl VisibilityConstraints {
652652
let ty = infer_expression_type(db, test_expr);
653653
ty.bool(db).negate_if(!predicate.is_positive)
654654
}
655+
PredicateNode::ReturnsNever(test_expr) => {
656+
let ty = infer_expression_type(db, test_expr);
657+
if let Type::FunctionLiteral(function_literal) = ty {
658+
let returns_never = if function_literal
659+
.signature(db)
660+
.overloads
661+
.overloads
662+
.iter()
663+
.all(|overload| {
664+
// HACK: for now, require that *all* overloads are annotated with
665+
// returning `Never`
666+
// Ideally, if only some overloads return `Never`, we should consider
667+
// the types of the arguments.
668+
overload.return_ty.is_some_and(|return_type| {
669+
return_type.is_equivalent_to(db, Type::Never)
670+
})
671+
}) {
672+
Truthiness::AlwaysTrue
673+
} else {
674+
Truthiness::AlwaysFalse
675+
};
676+
returns_never.negate_if(!predicate.is_positive)
677+
} else {
678+
// Should I add a panic here?
679+
// What about methods / other callables which are not functions?
680+
Truthiness::AlwaysTrue
681+
}
682+
}
655683
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
656684
PredicateNode::StarImportPlaceholder(star_import) => {
657685
let symbol_table = symbol_table(db, star_import.scope(db));

0 commit comments

Comments
 (0)