[red-knot] Understand typing.Type (#14904)

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
InSync 2024-12-11 18:01:38 +07:00 committed by GitHub
parent c8d505c8ea
commit f30227c436
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 49 additions and 3 deletions

View file

@ -0,0 +1,32 @@
# `typing.Type`
## Annotation
`typing.Type` can be used interchangeably with `type`:
```py
from typing import Type
class A: ...
def _(c: Type, d: Type[A], e: Type[A]):
reveal_type(c) # revealed: type
reveal_type(d) # revealed: type[A]
c = d # fine
d = c # fine
```
## Inheritance
Inheriting from `Type` results in a MRO with `builtins.type` and `typing.Generic`. `Type` itself is
not a class.
```py
from typing import Type
class C(Type): ...
# Runtime value: `(C, type, typing.Generic, object)`
# TODO: Add `Generic` to the MRO
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[type], Literal[object]]
```

View file

@ -1757,6 +1757,8 @@ impl<'db> Type<'db> {
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
// equivalent to `type[object]`.
Type::ClassLiteral(_) | Type::SubclassOf(_) => self.to_instance(db),
// We treat `typing.Type` exactly the same as `builtins.type`:
Type::KnownInstance(KnownInstanceType::Type) => KnownClass::Type.to_instance(db),
Type::Union(union) => union.map(db, |element| element.in_type_expression(db)),
Type::Unknown => Type::Unknown,
// TODO map this to a new `Type::TypeVar` variant
@ -2136,6 +2138,8 @@ pub enum KnownInstanceType<'db> {
Never,
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
Any,
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
Type,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
@ -2154,6 +2158,7 @@ impl<'db> KnownInstanceType<'db> {
Self::NoReturn => "NoReturn",
Self::Never => "Never",
Self::Any => "Any",
Self::Type => "Type",
Self::TypeAliasType(_) => "TypeAliasType",
}
}
@ -2169,6 +2174,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::NoReturn
| Self::Never
| Self::Any
| Self::Type
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
}
}
@ -2183,6 +2189,7 @@ impl<'db> KnownInstanceType<'db> {
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::Any => "typing.Any",
Self::Type => "typing.Type",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
}
@ -2198,6 +2205,7 @@ impl<'db> KnownInstanceType<'db> {
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::Any => KnownClass::Object,
Self::Type => KnownClass::Object,
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
}
@ -2224,6 +2232,7 @@ impl<'db> KnownInstanceType<'db> {
("typing" | "typing_extensions", "Union") => Some(Self::Union),
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
("typing" | "typing_extensions", "Never") => Some(Self::Never),
("typing" | "typing_extensions", "Type") => Some(Self::Type),
_ => None,
}
}

View file

@ -4861,6 +4861,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
Type::Unknown
}
KnownInstanceType::Type => self.infer_subclass_of_type_expression(parameters),
KnownInstanceType::Any => Type::Any,
}
}

View file

@ -77,7 +77,7 @@ impl<'db> Mro<'db> {
// but it's a common case (i.e., worth optimizing for),
// and the `c3_merge` function requires lots of allocations.
[single_base] => {
let single_base = ClassBase::try_from_ty(*single_base).ok_or(*single_base);
let single_base = ClassBase::try_from_ty(*single_base, db).ok_or(*single_base);
single_base.map_or_else(
|invalid_base_ty| {
let bases_info = Box::from([(0, invalid_base_ty)]);
@ -102,7 +102,7 @@ impl<'db> Mro<'db> {
let mut invalid_bases = vec![];
for (i, base) in multiple_bases.iter().enumerate() {
match ClassBase::try_from_ty(*base).ok_or(*base) {
match ClassBase::try_from_ty(*base, db).ok_or(*base) {
Ok(valid_base) => valid_bases.push(valid_base),
Err(invalid_base) => invalid_bases.push((i, invalid_base)),
}
@ -341,7 +341,7 @@ impl<'db> ClassBase<'db> {
/// Attempt to resolve `ty` into a `ClassBase`.
///
/// Return `None` if `ty` is not an acceptable type for a class base.
fn try_from_ty(ty: Type<'db>) -> Option<Self> {
fn try_from_ty(ty: Type<'db>, db: &'db dyn Db) -> Option<Self> {
match ty {
Type::Any => Some(Self::Any),
Type::Unknown => Some(Self::Unknown),
@ -371,6 +371,10 @@ impl<'db> ClassBase<'db> {
| KnownInstanceType::Never
| KnownInstanceType::Optional => None,
KnownInstanceType::Any => Some(Self::Any),
// TODO: classes inheriting from `typing.Type` also have `Generic` in their MRO
KnownInstanceType::Type => {
ClassBase::try_from_ty(KnownClass::Type.to_class_literal(db), db)
}
},
}
}