Compare commits
2 Commits
david/gene
...
david/call
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eae76b6e1 | ||
|
|
ca8f75b2f4 |
@@ -2,27 +2,53 @@
|
||||
|
||||
## Basic functionality
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
`assert_never` makes sure that the type of the argument is `Never`.
|
||||
|
||||
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
|
||||
`type-assertion-failure` diagnostic is emitted.
|
||||
### Correct usage
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||
def _(never: Never):
|
||||
assert_never(never) # fine
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
If it is not, a `type-assertion-failure` diagnostic is emitted.
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def _():
|
||||
assert_never(0) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never("") # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never(None) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never([]) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never({}) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never(()) # error: [type-assertion-failure]
|
||||
|
||||
def _(flag: bool, never: Never):
|
||||
assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
||||
def _(any_: Any):
|
||||
assert_never(any_) # error: [type-assertion-failure]
|
||||
|
||||
def _(unknown: Unknown):
|
||||
assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assert_never.md - `assert_never` - Basic functionality
|
||||
mdtest name: assert_never.md - `assert_never` - Basic functionality - Diagnostics
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
|
||||
---
|
||||
|
||||
@@ -15,35 +15,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.
|
||||
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
|
||||
4 | def _():
|
||||
5 | assert_never(0) # error: [type-assertion-failure]
|
||||
6 |
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
7 | def _():
|
||||
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]
|
||||
9 |
|
||||
10 | def _():
|
||||
11 | assert_never(None) # error: [type-assertion-failure]
|
||||
12 |
|
||||
13 | def _():
|
||||
14 | assert_never([]) # error: [type-assertion-failure]
|
||||
15 |
|
||||
16 | def _():
|
||||
17 | assert_never({}) # error: [type-assertion-failure]
|
||||
18 |
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
21 |
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | 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
|
||||
--> src/mdtest_snippet.py:5:5
|
||||
|
|
||||
5 | assert_never(never) # fine
|
||||
6 |
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
4 | def _():
|
||||
5 | 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]
|
||||
6 |
|
||||
7 | def _():
|
||||
|
|
||||
info: `Never` and `Literal[0]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -54,13 +66,13 @@ 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]
|
||||
7 | def _():
|
||||
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]
|
||||
9 |
|
||||
10 | def _():
|
||||
|
|
||||
info: `Never` and `Literal[""]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -69,16 +81,15 @@ 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
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | def _():
|
||||
11 | 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]
|
||||
12 |
|
||||
13 | def _():
|
||||
|
|
||||
info: `Never` and `None` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -87,16 +98,15 @@ 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
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
|
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
13 | def _():
|
||||
14 | 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]
|
||||
15 |
|
||||
16 | def _():
|
||||
|
|
||||
info: `Never` and `list[Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -105,16 +115,15 @@ 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
|
||||
--> src/mdtest_snippet.py:17:5
|
||||
|
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
16 | def _():
|
||||
17 | 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]
|
||||
18 |
|
||||
19 | def _():
|
||||
|
|
||||
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -123,15 +132,15 @@ 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
|
||||
--> src/mdtest_snippet.py:20:5
|
||||
|
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `tuple[()]`
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
21 |
|
||||
22 | def _(flag: bool, never: Never):
|
||||
|
|
||||
info: `Never` and `tuple[()]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -140,16 +149,15 @@ 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
|
||||
--> src/mdtest_snippet.py:23: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]
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | 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]
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
|
|
||||
info: `Never` and `Literal[1]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -158,15 +166,15 @@ 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
|
||||
--> src/mdtest_snippet.py:26:5
|
||||
|
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `Any`
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
|
|
||||
info: `Never` and `Any` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -175,10 +183,10 @@ 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
|
||||
--> src/mdtest_snippet.py:29:5
|
||||
|
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-------^
|
||||
| |
|
||||
| Inferred type of argument is `Unknown`
|
||||
@@ -570,6 +570,69 @@ def f():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Calls to functions returning `Never` / `NoReturn`
|
||||
|
||||
### No implicit return
|
||||
|
||||
If we see a call to a function returning `Never`, we should be able to understand that the function
|
||||
cannot implicitly return `None`. In the below examples, verify that there are no errors emitted for
|
||||
invalid return type.
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
def f() -> NoReturn:
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
Let's try cases where the function annotated with `NoReturn` is some sub-expression.
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
# TODO: this is currently not yet supported
|
||||
# error: [invalid-return-type]
|
||||
def _() -> NoReturn:
|
||||
3 + sys.exit(1)
|
||||
|
||||
# TODO: this is currently not yet supported
|
||||
# error: [invalid-return-type]
|
||||
def _() -> NoReturn:
|
||||
3 if sys.exit(1) else 4
|
||||
```
|
||||
|
||||
### Type narrowing
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
def g(x: int | None):
|
||||
if x is None:
|
||||
sys.exit(1)
|
||||
|
||||
# TODO: should be just int, not int | None
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
### Bindings after call
|
||||
|
||||
These should be understood to be unreachable.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
def _():
|
||||
x = 3
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
x = 4
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
|
||||
## Nested functions
|
||||
|
||||
Free references inside of a function body refer to variables defined in the containing scope.
|
||||
|
||||
@@ -8,6 +8,7 @@ hydpy # too many iterations
|
||||
ibis # too many iterations
|
||||
jax # too many iterations
|
||||
mypy # too many iterations (self-recursive type alias)
|
||||
nox # too many iterations (because of packaging)
|
||||
packaging # too many iterations
|
||||
pandas # slow (9s)
|
||||
pandera # too many iterations
|
||||
@@ -19,4 +20,6 @@ setuptools # vendors packaging, see above
|
||||
spack # slow, success, but mypy-primer hangs processing the output
|
||||
spark # too many iterations
|
||||
steam.py # hangs (single threaded)
|
||||
streamlit # too many iterations (because of packaging)
|
||||
tornado # bad use-def map (https://github.com/astral-sh/ty/issues/365)
|
||||
xarray # too many iterations
|
||||
|
||||
@@ -63,7 +63,6 @@ more-itertools
|
||||
mypy-protobuf
|
||||
mypy_primer
|
||||
nionutils
|
||||
nox
|
||||
openlibrary
|
||||
operator
|
||||
optuna
|
||||
@@ -107,7 +106,6 @@ starlette
|
||||
static-frame
|
||||
stone
|
||||
strawberry
|
||||
streamlit
|
||||
svcs
|
||||
sympy
|
||||
tornado
|
||||
|
||||
@@ -1901,11 +1901,28 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) if self.in_module_scope() => {
|
||||
if let Some(expr) = dunder_all_extend_argument(value) {
|
||||
self.add_standalone_expression(expr);
|
||||
}) => {
|
||||
if self.in_module_scope() {
|
||||
if let Some(expr) = dunder_all_extend_argument(value) {
|
||||
self.add_standalone_expression(expr);
|
||||
}
|
||||
}
|
||||
|
||||
self.visit_expr(value);
|
||||
|
||||
if !self.source_type.is_stub() {
|
||||
if let ast::Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||
let expression = self.add_standalone_expression(func);
|
||||
|
||||
let predicate = Predicate {
|
||||
node: PredicateNode::ReturnsNever(expression),
|
||||
is_positive: false,
|
||||
};
|
||||
self.record_reachability_constraint(PredicateOrLiteral::Predicate(
|
||||
predicate,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
walk_stmt(self, stmt);
|
||||
|
||||
@@ -105,6 +105,7 @@ impl PredicateOrLiteral<'_> {
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(crate) enum PredicateNode<'db> {
|
||||
Expression(Expression<'db>),
|
||||
ReturnsNever(Expression<'db>),
|
||||
Pattern(PatternPredicate<'db>),
|
||||
StarImportPlaceholder(StarImportPlaceholderPredicate<'db>),
|
||||
}
|
||||
|
||||
@@ -684,6 +684,35 @@ impl ReachabilityConstraints {
|
||||
let ty = infer_expression_type(db, test_expr);
|
||||
ty.bool(db).negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(test_expr) => {
|
||||
let ty = infer_expression_type(db, test_expr);
|
||||
if let Type::FunctionLiteral(function_literal) = ty {
|
||||
let returns_never =
|
||||
if function_literal
|
||||
.signature(db)
|
||||
.overloads
|
||||
.iter()
|
||||
.all(|overload| {
|
||||
// HACK: for now, require that *all* overloads are annotated with
|
||||
// returning `Never`
|
||||
// Ideally, if only some overloads return `Never`, we should consider
|
||||
// the types of the arguments.
|
||||
overload.return_ty.is_some_and(|return_type| {
|
||||
return_type.is_equivalent_to(db, Type::Never)
|
||||
})
|
||||
})
|
||||
{
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
};
|
||||
returns_never.negate_if(!predicate.is_positive)
|
||||
} else {
|
||||
// Should I add a panic here?
|
||||
// What about methods / other callables which are not functions?
|
||||
Truthiness::AlwaysTrue
|
||||
}
|
||||
}
|
||||
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
|
||||
@@ -192,6 +192,17 @@
|
||||
//! for that place that we need for that use or definition. When we reach the end of the scope, it
|
||||
//! records the state for each place as the public definitions of that place.
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1
|
||||
//! x = 2
|
||||
//! y = x
|
||||
//! if flag:
|
||||
//! x = 3
|
||||
//! else:
|
||||
//! x = 4
|
||||
//! z = x
|
||||
//! ```
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
|
||||
//! the new place (before we process the first binding), we create a new undefined `PlaceState`
|
||||
//! which has a single live binding (the "unbound" definition) and a single live declaration (the
|
||||
|
||||
@@ -5285,7 +5285,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// arguments after matching them to parameters, but before checking that the argument types
|
||||
// are assignable to any parameter annotations.
|
||||
let call_arguments = Self::parse_arguments(arguments);
|
||||
let callable_type = self.infer_expression(func);
|
||||
|
||||
let callable_type = self.infer_maybe_standalone_expression(func);
|
||||
|
||||
if let Type::FunctionLiteral(function) = callable_type {
|
||||
// Make sure that the `function.definition` is only called when the function is defined
|
||||
|
||||
@@ -60,6 +60,7 @@ pub(crate) fn infer_narrowing_constraint<'db>(
|
||||
all_negative_narrowing_constraints_for_pattern(db, pattern)
|
||||
}
|
||||
}
|
||||
PredicateNode::ReturnsNever(_) => return None,
|
||||
PredicateNode::StarImportPlaceholder(_) => return None,
|
||||
};
|
||||
if let Some(constraints) = constraints {
|
||||
@@ -347,6 +348,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
PredicateNode::Pattern(pattern) => {
|
||||
self.evaluate_pattern_predicate(pattern, self.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(_) => return None,
|
||||
PredicateNode::StarImportPlaceholder(_) => return None,
|
||||
};
|
||||
if let Some(mut constraints) = constraints {
|
||||
@@ -430,6 +432,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
match self.predicate {
|
||||
PredicateNode::Expression(expression) => expression.scope(self.db),
|
||||
PredicateNode::Pattern(pattern) => pattern.scope(self.db),
|
||||
PredicateNode::ReturnsNever(expression) => expression.scope(self.db),
|
||||
PredicateNode::StarImportPlaceholder(definition) => definition.scope(self.db),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user