Skip to content

Commit 461d262

Browse files
AlexWaygoodGlyphack
authored andcommitted
[ty] Improve diagnostics for assert_type and assert_never (astral-sh#18050)
1 parent f36e27c commit 461d262

File tree

6 files changed

+278
-21
lines changed

6 files changed

+278
-21
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Basic functionality
44

5+
<!-- snapshot-diagnostics -->
6+
57
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
68
`type-assertion-failure` diagnostic is emitted.
79

@@ -58,7 +60,7 @@ def if_else_isinstance_error(obj: A | B):
5860
elif isinstance(obj, C):
5961
pass
6062
else:
61-
# error: [type-assertion-failure] "Expected type `Never`, got `B & ~A & ~C` instead"
63+
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
6264
assert_never(obj)
6365

6466
def if_else_singletons_success(obj: Literal[1, "a"] | None):
@@ -79,7 +81,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None):
7981
elif obj is None:
8082
pass
8183
else:
82-
# error: [type-assertion-failure] "Expected type `Never`, got `Literal["a"]` instead"
84+
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
8385
assert_never(obj)
8486

8587
def match_singletons_success(obj: Literal[1, "a"] | None):
@@ -92,7 +94,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None):
9294
pass
9395
case _ as obj:
9496
# TODO: Ideally, we would not emit an error here
95-
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
97+
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
9698
assert_never(obj)
9799

98100
def match_singletons_error(obj: Literal[1, "a"] | None):
@@ -106,6 +108,6 @@ def match_singletons_error(obj: Literal[1, "a"] | None):
106108
case _ as obj:
107109
# TODO: We should emit an error here, but the message should
108110
# show the type `Literal["a"]` instead of `@Todo(…)`.
109-
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
111+
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
110112
assert_never(obj)
111113
```

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Basic
44

5+
<!-- snapshot-diagnostics -->
6+
57
```py
68
from typing_extensions import assert_type
79

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

crates/ty_python_semantic/src/types/diagnostic.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::types::string_annotation::{
1111
RAW_STRING_TYPE_ANNOTATION,
1212
};
1313
use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type};
14-
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
14+
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
1515
use ruff_python_ast::{self as ast, AnyNodeRef};
1616
use ruff_text_size::{Ranged, TextRange};
1717
use rustc_hash::FxHashSet;
@@ -1543,7 +1543,7 @@ pub(super) fn report_invalid_return_type(
15431543
return;
15441544
};
15451545

1546-
let return_type_span = Span::from(context.file()).with_range(return_type_range.range());
1546+
let return_type_span = context.span(return_type_range);
15471547

15481548
let mut diag = builder.into_diagnostic("Return type does not match returned value");
15491549
diag.set_primary_message(format_args!(
@@ -1849,16 +1849,13 @@ pub(crate) fn report_duplicate_bases(
18491849
),
18501850
);
18511851
sub_diagnostic.annotate(
1852-
Annotation::secondary(
1853-
Span::from(context.file()).with_range(bases_list[*first_index].range()),
1854-
)
1855-
.message(format_args!(
1852+
Annotation::secondary(context.span(&bases_list[*first_index])).message(format_args!(
18561853
"Class `{duplicate_name}` first included in bases list here"
18571854
)),
18581855
);
18591856
for index in later_indices {
18601857
sub_diagnostic.annotate(
1861-
Annotation::primary(Span::from(context.file()).with_range(bases_list[*index].range()))
1858+
Annotation::primary(context.span(&bases_list[*index]))
18621859
.message(format_args!("Class `{duplicate_name}` later repeated here")),
18631860
);
18641861
}

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4925,12 +4925,27 @@ impl<'db> TypeInferenceBuilder<'db> {
49254925
&TYPE_ASSERTION_FAILURE,
49264926
call_expression,
49274927
) {
4928-
builder.into_diagnostic(format_args!(
4929-
"Actual type `{}` is not the same \
4930-
as asserted type `{}`",
4931-
actual_ty.display(self.db()),
4932-
asserted_ty.display(self.db()),
4933-
));
4928+
let mut diagnostic =
4929+
builder.into_diagnostic(format_args!(
4930+
"Argument does not have asserted type `{}`",
4931+
asserted_ty.display(self.db()),
4932+
));
4933+
diagnostic.annotate(
4934+
Annotation::secondary(self.context.span(
4935+
&call_expression.arguments.args[0],
4936+
))
4937+
.message(format_args!(
4938+
"Inferred type of argument is `{}`",
4939+
actual_ty.display(self.db()),
4940+
)),
4941+
);
4942+
diagnostic.info(
4943+
format_args!(
4944+
"`{asserted_type}` and `{inferred_type}` are not equivalent types",
4945+
asserted_type = asserted_ty.display(self.db()),
4946+
inferred_type = actual_ty.display(self.db()),
4947+
)
4948+
);
49344949
}
49354950
}
49364951
}
@@ -4942,10 +4957,24 @@ impl<'db> TypeInferenceBuilder<'db> {
49424957
&TYPE_ASSERTION_FAILURE,
49434958
call_expression,
49444959
) {
4945-
builder.into_diagnostic(format_args!(
4946-
"Expected type `Never`, got `{}` instead",
4947-
actual_ty.display(self.db()),
4948-
));
4960+
let mut diagnostic = builder.into_diagnostic(
4961+
"Argument does not have asserted type `Never`",
4962+
);
4963+
diagnostic.annotate(
4964+
Annotation::secondary(self.context.span(
4965+
&call_expression.arguments.args[0],
4966+
))
4967+
.message(format_args!(
4968+
"Inferred type of argument is `{}`",
4969+
actual_ty.display(self.db())
4970+
)),
4971+
);
4972+
diagnostic.info(
4973+
format_args!(
4974+
"`Never` and `{inferred_type}` are not equivalent types",
4975+
inferred_type = actual_ty.display(self.db()),
4976+
)
4977+
);
49494978
}
49504979
}
49514980
}

0 commit comments

Comments
 (0)