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:
Shaygan Hooshyari 2024-11-25 22:37:55 +01:00 committed by GitHub
parent f98eebdbab
commit 557d583e32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 101 additions and 8 deletions

View file

@ -1553,6 +1553,9 @@ impl<'db> Type<'db> {
// TODO map this to a new `Type::TypeVar` variant
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
Type::Never
}
_ => todo_type!(),
}
}
@ -1889,6 +1892,10 @@ pub enum KnownInstanceType<'db> {
Optional,
/// The symbol `typing.Union` (which can also be found as `typing_extensions.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`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
@ -1899,11 +1906,13 @@ pub enum KnownInstanceType<'db> {
impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
KnownInstanceType::Literal => "Literal",
KnownInstanceType::Optional => "Optional",
KnownInstanceType::Union => "Union",
KnownInstanceType::TypeVar(_) => "TypeVar",
KnownInstanceType::TypeAliasType(_) => "TypeAliasType",
Self::Literal => "Literal",
Self::Optional => "Optional",
Self::Union => "Union",
Self::TypeVar(_) => "TypeVar",
Self::NoReturn => "NoReturn",
Self::Never => "Never",
Self::TypeAliasType(_) => "TypeAliasType",
}
}
@ -1914,6 +1923,8 @@ impl<'db> KnownInstanceType<'db> {
| Self::Optional
| Self::TypeVar(_)
| Self::Union
| Self::NoReturn
| Self::Never
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
}
}
@ -1924,6 +1935,8 @@ impl<'db> KnownInstanceType<'db> {
Self::Literal => "typing.Literal",
Self::Optional => "typing.Optional",
Self::Union => "typing.Union",
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
}
@ -1935,6 +1948,8 @@ impl<'db> KnownInstanceType<'db> {
Self::Literal => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::Union => KnownClass::SpecialForm,
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
}
@ -1957,6 +1972,8 @@ impl<'db> KnownInstanceType<'db> {
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
("typing" | "typing_extensions", "Union") => Some(Self::Union),
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
("typing" | "typing_extensions", "Never") => Some(Self::Never),
_ => None,
}
}

View file

@ -4575,7 +4575,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match value_ty {
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);
@ -4586,9 +4586,10 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_parameterized_known_instance_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
known_instance: KnownInstanceType,
parameters: &ast::Expr,
) -> Type<'db> {
let parameters = &*subscript.slice;
match known_instance {
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
Ok(ty) => ty,
@ -4629,6 +4630,17 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_type_expression(parameters);
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
}
}
}

View file

@ -375,6 +375,8 @@ impl<'db> ClassBase<'db> {
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::Literal
| KnownInstanceType::Union
| KnownInstanceType::NoReturn
| KnownInstanceType::Never
| KnownInstanceType::Optional => None,
},
}