mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
Support typing.NoReturn
and typing.Never
(#14559)
Fix #14558 ## Summary - Add `typing.NoReturn` and `typing.Never` to known instances and infer them as `Type::Never` - Add `is_assignable_to` cases for `Type::Never` I skipped emitting diagnostic for when a function is annotated as `NoReturn` but it actually returns. ## Test Plan Added tests from https://github.com/python/typing/blob/main/conformance/tests/specialtypes_never.py except from generics and checking if the return value of the function and the annotations match. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
f98eebdbab
commit
557d583e32
5 changed files with 101 additions and 8 deletions
|
@ -0,0 +1,62 @@
|
||||||
|
# NoReturn & Never
|
||||||
|
|
||||||
|
`NoReturn` is used to annotate the return type for functions that never return. `Never` is the
|
||||||
|
bottom type, representing the empty set of Python objects. These two annotations can be used
|
||||||
|
interchangeably.
|
||||||
|
|
||||||
|
## Function Return Type Annotation
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
|
def stop() -> NoReturn:
|
||||||
|
raise RuntimeError("no way")
|
||||||
|
|
||||||
|
# revealed: Never
|
||||||
|
reveal_type(stop())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assignment
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import NoReturn, Never, Any
|
||||||
|
|
||||||
|
# error: [invalid-type-parameter] "Type `typing.Never` expected no type parameter"
|
||||||
|
x: Never[int]
|
||||||
|
a1: NoReturn
|
||||||
|
# TODO: Test `Never` is only available in python >= 3.11
|
||||||
|
a2: Never
|
||||||
|
b1: Any
|
||||||
|
b2: int
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# revealed: Never
|
||||||
|
reveal_type(a1)
|
||||||
|
# revealed: Never
|
||||||
|
reveal_type(a2)
|
||||||
|
|
||||||
|
# Never is assignable to all types.
|
||||||
|
v1: int = a1
|
||||||
|
v2: str = a1
|
||||||
|
# Other types are not assignable to Never except for Never (and Any).
|
||||||
|
v3: Never = b1
|
||||||
|
v4: Never = a2
|
||||||
|
v5: Any = b2
|
||||||
|
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Never`"
|
||||||
|
v6: Never = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typing Extensions
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import NoReturn, Never
|
||||||
|
|
||||||
|
x: NoReturn
|
||||||
|
y: Never
|
||||||
|
|
||||||
|
def f():
|
||||||
|
# revealed: Never
|
||||||
|
reveal_type(x)
|
||||||
|
# revealed: Never
|
||||||
|
reveal_type(y)
|
||||||
|
```
|
|
@ -27,7 +27,7 @@ reveal_type(1 if 0 else 2) # revealed: Literal[2]
|
||||||
|
|
||||||
(issue #14588)
|
(issue #14588)
|
||||||
|
|
||||||
The test inside an if expression should not affect code outside of the block.
|
The test inside an if expression should not affect code outside of the expression.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def bool_instance() -> bool:
|
def bool_instance() -> bool:
|
||||||
|
|
|
@ -1553,6 +1553,9 @@ impl<'db> Type<'db> {
|
||||||
// TODO map this to a new `Type::TypeVar` variant
|
// TODO map this to a new `Type::TypeVar` variant
|
||||||
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
|
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
|
||||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
|
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
|
||||||
|
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
|
||||||
|
Type::Never
|
||||||
|
}
|
||||||
_ => todo_type!(),
|
_ => todo_type!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1889,6 +1892,10 @@ pub enum KnownInstanceType<'db> {
|
||||||
Optional,
|
Optional,
|
||||||
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
|
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
|
||||||
Union,
|
Union,
|
||||||
|
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
|
||||||
|
NoReturn,
|
||||||
|
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
|
||||||
|
Never,
|
||||||
/// A single instance of `typing.TypeVar`
|
/// A single instance of `typing.TypeVar`
|
||||||
TypeVar(TypeVarInstance<'db>),
|
TypeVar(TypeVarInstance<'db>),
|
||||||
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
||||||
|
@ -1899,11 +1906,13 @@ pub enum KnownInstanceType<'db> {
|
||||||
impl<'db> KnownInstanceType<'db> {
|
impl<'db> KnownInstanceType<'db> {
|
||||||
pub const fn as_str(self) -> &'static str {
|
pub const fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
KnownInstanceType::Literal => "Literal",
|
Self::Literal => "Literal",
|
||||||
KnownInstanceType::Optional => "Optional",
|
Self::Optional => "Optional",
|
||||||
KnownInstanceType::Union => "Union",
|
Self::Union => "Union",
|
||||||
KnownInstanceType::TypeVar(_) => "TypeVar",
|
Self::TypeVar(_) => "TypeVar",
|
||||||
KnownInstanceType::TypeAliasType(_) => "TypeAliasType",
|
Self::NoReturn => "NoReturn",
|
||||||
|
Self::Never => "Never",
|
||||||
|
Self::TypeAliasType(_) => "TypeAliasType",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1914,6 +1923,8 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
| Self::Optional
|
| Self::Optional
|
||||||
| Self::TypeVar(_)
|
| Self::TypeVar(_)
|
||||||
| Self::Union
|
| Self::Union
|
||||||
|
| Self::NoReturn
|
||||||
|
| Self::Never
|
||||||
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
|
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1924,6 +1935,8 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
Self::Literal => "typing.Literal",
|
Self::Literal => "typing.Literal",
|
||||||
Self::Optional => "typing.Optional",
|
Self::Optional => "typing.Optional",
|
||||||
Self::Union => "typing.Union",
|
Self::Union => "typing.Union",
|
||||||
|
Self::NoReturn => "typing.NoReturn",
|
||||||
|
Self::Never => "typing.Never",
|
||||||
Self::TypeVar(typevar) => typevar.name(db),
|
Self::TypeVar(typevar) => typevar.name(db),
|
||||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||||
}
|
}
|
||||||
|
@ -1935,6 +1948,8 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
Self::Literal => KnownClass::SpecialForm,
|
Self::Literal => KnownClass::SpecialForm,
|
||||||
Self::Optional => KnownClass::SpecialForm,
|
Self::Optional => KnownClass::SpecialForm,
|
||||||
Self::Union => KnownClass::SpecialForm,
|
Self::Union => KnownClass::SpecialForm,
|
||||||
|
Self::NoReturn => KnownClass::SpecialForm,
|
||||||
|
Self::Never => KnownClass::SpecialForm,
|
||||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||||
}
|
}
|
||||||
|
@ -1957,6 +1972,8 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
|
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
|
||||||
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
|
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
|
||||||
("typing" | "typing_extensions", "Union") => Some(Self::Union),
|
("typing" | "typing_extensions", "Union") => Some(Self::Union),
|
||||||
|
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
|
||||||
|
("typing" | "typing_extensions", "Never") => Some(Self::Never),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4575,7 +4575,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
match value_ty {
|
match value_ty {
|
||||||
Type::KnownInstance(known_instance) => {
|
Type::KnownInstance(known_instance) => {
|
||||||
self.infer_parameterized_known_instance_type_expression(known_instance, slice)
|
self.infer_parameterized_known_instance_type_expression(subscript, known_instance)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.infer_type_expression(slice);
|
self.infer_type_expression(slice);
|
||||||
|
@ -4586,9 +4586,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
fn infer_parameterized_known_instance_type_expression(
|
fn infer_parameterized_known_instance_type_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
subscript: &ast::ExprSubscript,
|
||||||
known_instance: KnownInstanceType,
|
known_instance: KnownInstanceType,
|
||||||
parameters: &ast::Expr,
|
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
|
let parameters = &*subscript.slice;
|
||||||
match known_instance {
|
match known_instance {
|
||||||
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
|
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
|
||||||
Ok(ty) => ty,
|
Ok(ty) => ty,
|
||||||
|
@ -4629,6 +4630,17 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
self.infer_type_expression(parameters);
|
self.infer_type_expression(parameters);
|
||||||
todo_type!("generic type alias")
|
todo_type!("generic type alias")
|
||||||
}
|
}
|
||||||
|
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
|
||||||
|
self.diagnostics.add(
|
||||||
|
subscript.into(),
|
||||||
|
"invalid-type-parameter",
|
||||||
|
format_args!(
|
||||||
|
"Type `{}` expected no type parameter",
|
||||||
|
known_instance.repr(self.db)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Type::Unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -375,6 +375,8 @@ impl<'db> ClassBase<'db> {
|
||||||
| KnownInstanceType::TypeAliasType(_)
|
| KnownInstanceType::TypeAliasType(_)
|
||||||
| KnownInstanceType::Literal
|
| KnownInstanceType::Literal
|
||||||
| KnownInstanceType::Union
|
| KnownInstanceType::Union
|
||||||
|
| KnownInstanceType::NoReturn
|
||||||
|
| KnownInstanceType::Never
|
||||||
| KnownInstanceType::Optional => None,
|
| KnownInstanceType::Optional => None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue