mirror of https://github.com/astral-sh/ruff
[ty] Improve diagnostics for `assert_type` and `assert_never` (#18050)
This commit is contained in:
parent
00f672a83b
commit
5913997c72
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Basic functionality
|
## Basic functionality
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
|
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
|
||||||
`type-assertion-failure` diagnostic is emitted.
|
`type-assertion-failure` diagnostic is emitted.
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ def if_else_isinstance_error(obj: A | B):
|
||||||
elif isinstance(obj, C):
|
elif isinstance(obj, C):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# error: [type-assertion-failure] "Expected type `Never`, got `B & ~A & ~C` instead"
|
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||||
assert_never(obj)
|
assert_never(obj)
|
||||||
|
|
||||||
def if_else_singletons_success(obj: Literal[1, "a"] | None):
|
def if_else_singletons_success(obj: Literal[1, "a"] | None):
|
||||||
|
|
@ -79,7 +81,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None):
|
||||||
elif obj is None:
|
elif obj is None:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# error: [type-assertion-failure] "Expected type `Never`, got `Literal["a"]` instead"
|
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||||
assert_never(obj)
|
assert_never(obj)
|
||||||
|
|
||||||
def match_singletons_success(obj: Literal[1, "a"] | None):
|
def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||||
|
|
@ -92,7 +94,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||||
pass
|
pass
|
||||||
case _ as obj:
|
case _ as obj:
|
||||||
# TODO: Ideally, we would not emit an error here
|
# TODO: Ideally, we would not emit an error here
|
||||||
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
|
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||||
assert_never(obj)
|
assert_never(obj)
|
||||||
|
|
||||||
def match_singletons_error(obj: Literal[1, "a"] | None):
|
def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||||
|
|
@ -106,6 +108,6 @@ def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||||
case _ as obj:
|
case _ as obj:
|
||||||
# TODO: We should emit an error here, but the message should
|
# TODO: We should emit an error here, but the message should
|
||||||
# show the type `Literal["a"]` instead of `@Todo(…)`.
|
# show the type `Literal["a"]` instead of `@Todo(…)`.
|
||||||
# error: [type-assertion-failure] "Expected type `Never`, got `@Todo"
|
# error: [type-assertion-failure] "Argument does not have asserted type `Never`"
|
||||||
assert_never(obj)
|
assert_never(obj)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Basic
|
## Basic
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import assert_type
|
from typing_extensions import assert_type
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: assert_never.md - `assert_never` - Basic functionality
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing_extensions import assert_never, Never, Any
|
||||||
|
2 | from ty_extensions import Unknown
|
||||||
|
3 |
|
||||||
|
4 | def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||||
|
5 | assert_never(never) # fine
|
||||||
|
6 |
|
||||||
|
7 | assert_never(0) # error: [type-assertion-failure]
|
||||||
|
8 | assert_never("") # error: [type-assertion-failure]
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
12 | assert_never(()) # error: [type-assertion-failure]
|
||||||
|
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||||
|
14 |
|
||||||
|
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||||
|
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:7:5
|
||||||
|
|
|
||||||
|
5 | assert_never(never) # fine
|
||||||
|
6 |
|
||||||
|
7 | assert_never(0) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^-^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `Literal[0]`
|
||||||
|
8 | assert_never("") # error: [type-assertion-failure]
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `Literal[0]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:8:5
|
||||||
|
|
|
||||||
|
7 | assert_never(0) # error: [type-assertion-failure]
|
||||||
|
8 | assert_never("") # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^--^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `Literal[""]`
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `Literal[""]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:9:5
|
||||||
|
|
|
||||||
|
7 | assert_never(0) # error: [type-assertion-failure]
|
||||||
|
8 | assert_never("") # error: [type-assertion-failure]
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^----^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `None`
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `None` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:10:5
|
||||||
|
|
|
||||||
|
8 | assert_never("") # error: [type-assertion-failure]
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^--^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `list[Unknown]`
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
12 | assert_never(()) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `list[Unknown]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:11:5
|
||||||
|
|
|
||||||
|
9 | assert_never(None) # error: [type-assertion-failure]
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^--^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `dict[Unknown, Unknown]`
|
||||||
|
12 | assert_never(()) # error: [type-assertion-failure]
|
||||||
|
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:12:5
|
||||||
|
|
|
||||||
|
10 | assert_never([]) # error: [type-assertion-failure]
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
12 | assert_never(()) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^--^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `tuple[()]`
|
||||||
|
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `tuple[()]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:13:5
|
||||||
|
|
|
||||||
|
11 | assert_never({}) # error: [type-assertion-failure]
|
||||||
|
12 | assert_never(()) # error: [type-assertion-failure]
|
||||||
|
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^--------------------^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `Literal[1]`
|
||||||
|
14 |
|
||||||
|
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `Literal[1]` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:15:5
|
||||||
|
|
|
||||||
|
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||||
|
14 |
|
||||||
|
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^----^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `Any`
|
||||||
|
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||||
|
|
|
||||||
|
info: `Never` and `Any` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||||
|
--> src/mdtest_snippet.py:16:5
|
||||||
|
|
|
||||||
|
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||||
|
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^^-------^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `Unknown`
|
||||||
|
|
|
||||||
|
info: `Never` and `Unknown` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: assert_type.md - `assert_type` - Basic
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_type.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing_extensions import assert_type
|
||||||
|
2 |
|
||||||
|
3 | def _(x: int):
|
||||||
|
4 | assert_type(x, int) # fine
|
||||||
|
5 | assert_type(x, str) # error: [type-assertion-failure]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[type-assertion-failure]: Argument does not have asserted type `str`
|
||||||
|
--> src/mdtest_snippet.py:5:5
|
||||||
|
|
|
||||||
|
3 | def _(x: int):
|
||||||
|
4 | assert_type(x, int) # fine
|
||||||
|
5 | assert_type(x, str) # error: [type-assertion-failure]
|
||||||
|
| ^^^^^^^^^^^^-^^^^^^
|
||||||
|
| |
|
||||||
|
| Inferred type of argument is `int`
|
||||||
|
|
|
||||||
|
info: `str` and `int` are not equivalent types
|
||||||
|
info: rule `type-assertion-failure` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::types::string_annotation::{
|
||||||
RAW_STRING_TYPE_ANNOTATION,
|
RAW_STRING_TYPE_ANNOTATION,
|
||||||
};
|
};
|
||||||
use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type};
|
use crate::types::{protocol_class::ProtocolClassLiteral, KnownFunction, KnownInstanceType, Type};
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
@ -1543,7 +1543,7 @@ pub(super) fn report_invalid_return_type(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let return_type_span = Span::from(context.file()).with_range(return_type_range.range());
|
let return_type_span = context.span(return_type_range);
|
||||||
|
|
||||||
let mut diag = builder.into_diagnostic("Return type does not match returned value");
|
let mut diag = builder.into_diagnostic("Return type does not match returned value");
|
||||||
diag.set_primary_message(format_args!(
|
diag.set_primary_message(format_args!(
|
||||||
|
|
@ -1849,16 +1849,13 @@ pub(crate) fn report_duplicate_bases(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
sub_diagnostic.annotate(
|
sub_diagnostic.annotate(
|
||||||
Annotation::secondary(
|
Annotation::secondary(context.span(&bases_list[*first_index])).message(format_args!(
|
||||||
Span::from(context.file()).with_range(bases_list[*first_index].range()),
|
|
||||||
)
|
|
||||||
.message(format_args!(
|
|
||||||
"Class `{duplicate_name}` first included in bases list here"
|
"Class `{duplicate_name}` first included in bases list here"
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
for index in later_indices {
|
for index in later_indices {
|
||||||
sub_diagnostic.annotate(
|
sub_diagnostic.annotate(
|
||||||
Annotation::primary(Span::from(context.file()).with_range(bases_list[*index].range()))
|
Annotation::primary(context.span(&bases_list[*index]))
|
||||||
.message(format_args!("Class `{duplicate_name}` later repeated here")),
|
.message(format_args!("Class `{duplicate_name}` later repeated here")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4834,12 +4834,27 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
&TYPE_ASSERTION_FAILURE,
|
&TYPE_ASSERTION_FAILURE,
|
||||||
call_expression,
|
call_expression,
|
||||||
) {
|
) {
|
||||||
|
let mut diagnostic =
|
||||||
builder.into_diagnostic(format_args!(
|
builder.into_diagnostic(format_args!(
|
||||||
"Actual type `{}` is not the same \
|
"Argument does not have asserted type `{}`",
|
||||||
as asserted type `{}`",
|
|
||||||
actual_ty.display(self.db()),
|
|
||||||
asserted_ty.display(self.db()),
|
asserted_ty.display(self.db()),
|
||||||
));
|
));
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(self.context.span(
|
||||||
|
&call_expression.arguments.args[0],
|
||||||
|
))
|
||||||
|
.message(format_args!(
|
||||||
|
"Inferred type of argument is `{}`",
|
||||||
|
actual_ty.display(self.db()),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
diagnostic.info(
|
||||||
|
format_args!(
|
||||||
|
"`{asserted_type}` and `{inferred_type}` are not equivalent types",
|
||||||
|
asserted_type = asserted_ty.display(self.db()),
|
||||||
|
inferred_type = actual_ty.display(self.db()),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4851,10 +4866,24 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
&TYPE_ASSERTION_FAILURE,
|
&TYPE_ASSERTION_FAILURE,
|
||||||
call_expression,
|
call_expression,
|
||||||
) {
|
) {
|
||||||
builder.into_diagnostic(format_args!(
|
let mut diagnostic = builder.into_diagnostic(
|
||||||
"Expected type `Never`, got `{}` instead",
|
"Argument does not have asserted type `Never`",
|
||||||
actual_ty.display(self.db()),
|
);
|
||||||
));
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(self.context.span(
|
||||||
|
&call_expression.arguments.args[0],
|
||||||
|
))
|
||||||
|
.message(format_args!(
|
||||||
|
"Inferred type of argument is `{}`",
|
||||||
|
actual_ty.display(self.db())
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
diagnostic.info(
|
||||||
|
format_args!(
|
||||||
|
"`Never` and `{inferred_type}` are not equivalent types",
|
||||||
|
inferred_type = actual_ty.display(self.db()),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue