[red-knot] Fix MRO inference for protocol classes; allow inheritance from subscripted `Generic[]`; forbid subclassing unsubscripted `Generic` (#17452)

This commit is contained in:
Alex Waygood 2025-04-18 20:55:53 +01:00 committed by GitHub
parent fd3fc34a9e
commit 454ad15aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 345 additions and 139 deletions

View File

@ -67,21 +67,24 @@ import typing
####################
### Built-ins
####################
class ListSubclass(typing.List): ...
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(ListSubclass.__mro__)
class DictSubclass(typing.Dict): ...
# TODO: should have `Generic`, should not have `Unknown`
# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[DictSubclass], Literal[dict], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(DictSubclass.__mro__)
class SetSubclass(typing.Set): ...
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(SetSubclass.__mro__)
class FrozenSetSubclass(typing.FrozenSet): ...
@ -92,11 +95,12 @@ reveal_type(FrozenSetSubclass.__mro__)
####################
### `collections`
####################
class ChainMapSubclass(typing.ChainMap): ...
# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(ChainMapSubclass.__mro__)
class CounterSubclass(typing.Counter): ...
@ -113,7 +117,8 @@ reveal_type(DefaultDictSubclass.__mro__)
class DequeSubclass(typing.Deque): ...
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(DequeSubclass.__mro__)
class OrderedDictSubclass(typing.OrderedDict): ...

View File

@ -41,7 +41,7 @@ class Foo:
One thing that is supported is error messages for using special forms in type expressions.
```py
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, Generic
def _(
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
@ -49,6 +49,7 @@ def _(
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
e: ParamSpec,
f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions"
) -> None:
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
@ -65,7 +66,7 @@ You can't inherit from most of these. `typing.Callable` is an exception.
```py
from typing import Callable
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
class A(Self): ... # error: [invalid-base]
class B(Unpack): ... # error: [invalid-base]
@ -73,6 +74,7 @@ class C(TypeGuard): ... # error: [invalid-base]
class D(TypeIs): ... # error: [invalid-base]
class E(Concatenate): ... # error: [invalid-base]
class F(Callable): ...
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`"
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
```

View File

@ -45,8 +45,6 @@ from typing import Generic, TypeVar
T = TypeVar("T")
# TODO: no error
# error: [invalid-base]
class C(Generic[T]): ...
```

View File

@ -137,8 +137,6 @@ from typing import TypeVar, Generic
T = TypeVar("T")
S = TypeVar("S")
# TODO: no error
# error: [invalid-base]
class Legacy(Generic[T]):
def m(self, x: T, y: S) -> S:
return y
@ -177,8 +175,6 @@ def f(x: T) -> None:
# TODO: invalid-assignment error
y: list[S] = []
# TODO: no error
# error: [invalid-base]
class C(Generic[T]):
# TODO: error: cannot use S if it's not in the current generic context
x: list[S] = []
@ -259,8 +255,7 @@ def f[T](x: T, y: T) -> None:
class Ok[S]: ...
# TODO: error for reuse of typevar
class Bad1[T]: ...
# TODO: no non-subscriptable error, error for reuse of typevar
# error: [non-subscriptable]
# TODO: error for reuse of typevar
class Bad2(Iterable[T]): ...
```
@ -273,8 +268,7 @@ class C[T]:
class Ok1[S]: ...
# TODO: error for reuse of typevar
class Bad1[T]: ...
# TODO: no non-subscriptable error, error for reuse of typevar
# error: [non-subscriptable]
# TODO: error for reuse of typevar
class Bad2(Iterable[T]): ...
```

View File

@ -28,8 +28,7 @@ from typing import Protocol
class MyProtocol(Protocol): ...
# TODO: at runtime this is `(<class '__main__.MyProtocol'>, <class 'typing.Protocol'>, <class 'typing.Generic'>, <class 'object'>)`
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], @Todo(protocol), Literal[object]]
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
```
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
@ -72,8 +71,7 @@ it is not sufficient for it to have `Protocol` in its MRO.
```py
class SubclassOfMyProtocol(MyProtocol): ...
# TODO
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], @Todo(protocol), Literal[object]]
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(SubclassOfMyProtocol.__mro__)
# TODO: should be `Literal[False]`
@ -94,8 +92,7 @@ class OtherProtocol(Protocol):
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
# TODO
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[object]]
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(ComplexInheritance.__mro__)
# TODO: should be `Literal[True]`
@ -109,15 +106,13 @@ or `TypeError` is raised at runtime when the class is created.
# TODO: should emit `[invalid-protocol]`
class Invalid(NotAProtocol, Protocol): ...
# TODO
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(Invalid.__mro__)
# TODO: should emit an `[invalid-protocol`] error
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
# TODO
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
reveal_type(AlsoInvalid.__mro__)
```
@ -135,11 +130,9 @@ T = TypeVar("T")
# type checkers.
class Fine(Protocol, object): ...
# TODO
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], @Todo(protocol), Literal[object]]
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], typing.Protocol, typing.Generic, Literal[object]]
# TODO: should not error
class StillFine(Protocol, Generic[T], object): ... # error: [invalid-base]
class StillFine(Protocol, Generic[T], object): ...
class EvenThis[T](Protocol, object): ...
```
@ -149,8 +142,7 @@ And multiple inheritance from a mix of protocol and non-protocol classes is fine
```py
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
# TODO
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[NotAProtocol], Literal[object]]
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[NotAProtocol], Literal[object]]
reveal_type(FineAndDandy.__mro__)
```

View File

@ -117,6 +117,7 @@ from typing import Tuple
class C(Tuple): ...
# revealed: tuple[Literal[C], Literal[tuple], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
# TODO: generic protocols
# revealed: tuple[Literal[C], Literal[tuple], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
reveal_type(C.__mro__)
```

View File

@ -26,7 +26,7 @@ pub(crate) use self::infer::{
};
pub(crate) use self::narrow::KnownConstraintFunction;
pub(crate) use self::signatures::{CallableSignature, Signature, Signatures};
pub(crate) use self::subclass_of::SubclassOfType;
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
use crate::module_name::ModuleName;
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId};
@ -499,7 +499,11 @@ impl<'db> Type<'db> {
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
match self {
Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
Self::Dynamic(
DynamicType::Todo(_)
| DynamicType::SubscriptedProtocol
| DynamicType::SubscriptedGeneric,
) => true,
Self::AlwaysFalsy
| Self::AlwaysTruthy
@ -540,9 +544,13 @@ impl<'db> Type<'db> {
}
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
ClassBase::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
ClassBase::Class(_) => false,
SubclassOfInner::Dynamic(
DynamicType::Todo(_)
| DynamicType::SubscriptedProtocol
| DynamicType::SubscriptedGeneric,
) => true,
SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
SubclassOfInner::Class(_) => false,
},
Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
@ -557,10 +565,18 @@ impl<'db> Type<'db> {
Self::BoundSuper(bound_super) => {
matches!(
bound_super.pivot_class(db),
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol)
ClassBase::Dynamic(
DynamicType::Todo(_)
| DynamicType::SubscriptedGeneric
| DynamicType::SubscriptedProtocol
)
) || matches!(
bound_super.owner(db),
SuperOwnerKind::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol)
SuperOwnerKind::Dynamic(
DynamicType::Todo(_)
| DynamicType::SubscriptedGeneric
| DynamicType::SubscriptedProtocol
)
)
}
@ -1466,7 +1482,7 @@ impl<'db> Type<'db> {
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
match (first.subclass_of(), second.subclass_of()) {
(first, second) if first == second => true,
(ClassBase::Dynamic(_), ClassBase::Dynamic(_)) => true,
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => true,
_ => false,
}
}
@ -1633,16 +1649,16 @@ impl<'db> Type<'db> {
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => false,
ClassBase::Class(class_a) => !class_b.is_subclass_of(db, None, class_a),
SubclassOfInner::Dynamic(_) => false,
SubclassOfInner::Class(class_a) => !class_b.is_subclass_of(db, None, class_a),
}
}
(Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => false,
ClassBase::Class(class_a) => {
SubclassOfInner::Dynamic(_) => false,
SubclassOfInner::Class(class_a) => {
!ClassType::from(alias_b).is_subclass_of(db, class_a)
}
}
@ -1691,10 +1707,10 @@ impl<'db> Type<'db> {
// so although the type is dynamic we can still determine disjointedness in some situations
(Type::SubclassOf(subclass_of_ty), other)
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => {
SubclassOfInner::Dynamic(_) => {
KnownClass::Type.to_instance(db).is_disjoint_from(db, other)
}
ClassBase::Class(class) => class
SubclassOfInner::Class(class) => class
.metaclass_instance_type(db)
.is_disjoint_from(db, other),
},
@ -3073,8 +3089,8 @@ impl<'db> Type<'db> {
.try_bool_impl(db, allow_short_circuit)?,
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => Truthiness::Ambiguous,
ClassBase::Class(class) => {
SubclassOfInner::Dynamic(_) => Truthiness::Ambiguous,
SubclassOfInner::Class(class) => {
Type::from(class).try_bool_impl(db, allow_short_circuit)?
}
},
@ -3812,12 +3828,14 @@ impl<'db> Type<'db> {
}
Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type).signatures(db),
SubclassOfInner::Dynamic(dynamic_type) => {
Type::Dynamic(dynamic_type).signatures(db)
}
// Most type[] constructor calls are handled by `try_call_constructor` and not via
// getting the signature here. This signature can still be used in some cases (e.g.
// evaluating callable subtyping). TODO improve this definition (intersection of
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
ClassBase::Class(class) => Type::from(class).signatures(db),
SubclassOfInner::Class(class) => Type::from(class).signatures(db),
},
Type::Instance(_) => {
@ -4380,6 +4398,10 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
fallback_type: Type::unknown(),
}),
KnownInstanceType::Generic => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic],
fallback_type: Type::unknown(),
}),
KnownInstanceType::Literal
| KnownInstanceType::Union
@ -4564,21 +4586,21 @@ impl<'db> Type<'db> {
Type::ClassLiteral(class) => class.metaclass(db),
Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db),
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => *self,
ClassBase::Class(class) => SubclassOfType::from(
SubclassOfInner::Dynamic(_) => *self,
SubclassOfInner::Class(class) => SubclassOfType::from(
db,
ClassBase::try_from_type(db, class.metaclass(db))
.unwrap_or(ClassBase::unknown()),
SubclassOfInner::try_from_type(db, class.metaclass(db))
.unwrap_or(SubclassOfInner::unknown()),
),
},
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
Type::Dynamic(dynamic) => SubclassOfType::from(db, ClassBase::Dynamic(*dynamic)),
Type::Dynamic(dynamic) => SubclassOfType::from(db, SubclassOfInner::Dynamic(*dynamic)),
// TODO intersections
Type::Intersection(_) => SubclassOfType::from(
db,
ClassBase::try_from_type(db, todo_type!("Intersection meta-type"))
.expect("Type::Todo should be a valid ClassBase"),
SubclassOfInner::try_from_type(db, todo_type!("Intersection meta-type"))
.expect("Type::Todo should be a valid `SubclassOfInner`"),
),
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
@ -4780,8 +4802,8 @@ impl<'db> Type<'db> {
},
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
ClassBase::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
ClassBase::Dynamic(_) => None,
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
SubclassOfInner::Dynamic(_) => None,
},
Self::StringLiteral(_)
@ -4833,9 +4855,12 @@ pub enum DynamicType {
///
/// This variant should be created with the `todo_type!` macro.
Todo(TodoType),
/// Temporary type until we support protocols. We use a separate variant (instead of `Todo(…)`)
/// in order to be able to match on them explicitly.
TodoProtocol,
/// Temporary type until we support generic protocols.
/// We use a separate variant (instead of `Todo(…)`) in order to be able to match on them explicitly.
SubscriptedProtocol,
/// Temporary type until we support old-style generics.
/// We use a separate variant (instead of `Todo(…)`) in order to be able to match on them explicitly.
SubscriptedGeneric,
}
impl std::fmt::Display for DynamicType {
@ -4846,8 +4871,13 @@ impl std::fmt::Display for DynamicType {
// `DynamicType::Todo`'s display should be explicit that is not a valid display of
// any other type
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
DynamicType::TodoProtocol => f.write_str(if cfg!(debug_assertions) {
"@Todo(protocol)"
DynamicType::SubscriptedProtocol => f.write_str(if cfg!(debug_assertions) {
"@Todo(`Protocol[]` subscript)"
} else {
"@Todo"
}),
DynamicType::SubscriptedGeneric => f.write_str(if cfg!(debug_assertions) {
"@Todo(`Generic[]` subscript)"
} else {
"@Todo"
}),
@ -4959,8 +4989,10 @@ enum InvalidTypeExpression<'db> {
RequiresArguments(Type<'db>),
/// Some types always require at least two arguments when used in a type expression
RequiresTwoArguments(Type<'db>),
/// The `Protocol` type is invalid in type expressions
/// The `Protocol` class is invalid in type expressions
Protocol,
/// Same for `Generic`
Generic,
/// Type qualifiers are always invalid in *type expressions*,
/// but these ones are okay with 0 arguments in *annotation expressions*
TypeQualifier(KnownInstanceType<'db>),
@ -4999,6 +5031,9 @@ impl<'db> InvalidTypeExpression<'db> {
InvalidTypeExpression::Protocol => f.write_str(
"`typing.Protocol` is not allowed in type expressions"
),
InvalidTypeExpression::Generic => f.write_str(
"`typing.Generic` is not allowed in type expressions"
),
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
f,
"Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)",

View File

@ -246,7 +246,7 @@ impl<'db> ClassType<'db> {
/// cases rather than simply iterating over the inferred resolution order for the class.
///
/// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
pub(super) fn iter_mro(self, db: &'db dyn Db) -> impl Iterator<Item = ClassBase<'db>> {
pub(super) fn iter_mro(self, db: &'db dyn Db) -> MroIterator<'db> {
let (class_literal, specialization) = self.class_literal(db);
class_literal.iter_mro(db, specialization)
}
@ -645,7 +645,7 @@ impl<'db> ClassLiteralType<'db> {
self,
db: &'db dyn Db,
specialization: Option<Specialization<'db>>,
) -> impl Iterator<Item = ClassBase<'db>> {
) -> MroIterator<'db> {
MroIterator::new(db, self, specialization)
}
@ -846,7 +846,11 @@ impl<'db> ClassLiteralType<'db> {
for superclass in mro_iter {
match superclass {
ClassBase::Dynamic(DynamicType::TodoProtocol) => {
ClassBase::Dynamic(
DynamicType::SubscriptedGeneric | DynamicType::SubscriptedProtocol,
)
| ClassBase::Generic
| ClassBase::Protocol => {
// TODO: We currently skip `Protocol` when looking up class members, in order to
// avoid creating many dynamic types in our test suite that would otherwise
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
@ -865,7 +869,12 @@ impl<'db> ClassLiteralType<'db> {
continue;
}
if class.is_known(db, KnownClass::Type) && policy.meta_class_no_type_fallback()
// HACK: we should implement some more general logic here that supports arbitrary custom
// metaclasses, not just `type` and `ABCMeta`.
if matches!(
class.known(db),
Some(KnownClass::Type | KnownClass::ABCMeta)
) && policy.meta_class_no_type_fallback()
{
continue;
}
@ -1158,8 +1167,12 @@ impl<'db> ClassLiteralType<'db> {
for superclass in self.iter_mro(db, specialization) {
match superclass {
ClassBase::Dynamic(DynamicType::TodoProtocol) => {
// TODO: We currently skip `Protocol` when looking up instance members, in order to
ClassBase::Dynamic(
DynamicType::SubscriptedProtocol | DynamicType::SubscriptedGeneric,
)
| ClassBase::Generic
| ClassBase::Protocol => {
// TODO: We currently skip these when looking up instance members, in order to
// avoid creating many dynamic types in our test suite that would otherwise
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
}
@ -1671,6 +1684,8 @@ pub(crate) enum KnownClass {
Super,
// enum
Enum,
// abc
ABCMeta,
// Types
GenericAlias,
ModuleType,
@ -1780,6 +1795,7 @@ impl<'db> KnownClass {
| Self::Float
| Self::Sized
| Self::Enum
| Self::ABCMeta
// Evaluating `NotImplementedType` in a boolean context was deprecated in Python 3.9
// and raises a `TypeError` in Python >=3.14
// (see https://docs.python.org/3/library/constants.html#NotImplemented)
@ -1836,6 +1852,7 @@ impl<'db> KnownClass {
Self::Sized => "Sized",
Self::OrderedDict => "OrderedDict",
Self::Enum => "Enum",
Self::ABCMeta => "ABCMeta",
Self::Super => "super",
// For example, `typing.List` is defined as `List = _Alias()` in typeshed
Self::StdlibAlias => "_Alias",
@ -1992,6 +2009,7 @@ impl<'db> KnownClass {
| Self::Super
| Self::Property => KnownModule::Builtins,
Self::VersionInfo => KnownModule::Sys,
Self::ABCMeta => KnownModule::Abc,
Self::Enum => KnownModule::Enum,
Self::GenericAlias
| Self::ModuleType
@ -2096,6 +2114,7 @@ impl<'db> KnownClass {
| Self::TypeVarTuple
| Self::Sized
| Self::Enum
| Self::ABCMeta
| Self::Super
| Self::NewType => false,
}
@ -2155,6 +2174,7 @@ impl<'db> KnownClass {
| Self::TypeVarTuple
| Self::Sized
| Self::Enum
| Self::ABCMeta
| Self::Super
| Self::UnionType
| Self::NewType => false,
@ -2216,6 +2236,7 @@ impl<'db> KnownClass {
"SupportsIndex" => Self::SupportsIndex,
"Sized" => Self::Sized,
"Enum" => Self::Enum,
"ABCMeta" => Self::ABCMeta,
"super" => Self::Super,
"_version_info" => Self::VersionInfo,
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
@ -2271,6 +2292,7 @@ impl<'db> KnownClass {
| Self::MethodType
| Self::MethodWrapperType
| Self::Enum
| Self::ABCMeta
| Self::Super
| Self::NotImplementedType
| Self::UnionType
@ -2393,6 +2415,8 @@ pub enum KnownInstanceType<'db> {
OrderedDict,
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
Protocol,
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
Generic,
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
Type,
/// A single instance of `typing.TypeVar`
@ -2467,6 +2491,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::ChainMap
| Self::OrderedDict
| Self::Protocol
| Self::Generic
| Self::ReadOnly
| Self::TypeAliasType(_)
| Self::Unknown
@ -2513,6 +2538,7 @@ impl<'db> KnownInstanceType<'db> {
Self::ChainMap => "typing.ChainMap",
Self::OrderedDict => "typing.OrderedDict",
Self::Protocol => "typing.Protocol",
Self::Generic => "typing.Generic",
Self::ReadOnly => "typing.ReadOnly",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
@ -2560,7 +2586,8 @@ impl<'db> KnownInstanceType<'db> {
Self::Deque => KnownClass::StdlibAlias,
Self::ChainMap => KnownClass::StdlibAlias,
Self::OrderedDict => KnownClass::StdlibAlias,
Self::Protocol => KnownClass::SpecialForm,
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
Self::Generic => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::TypeOf => KnownClass::SpecialForm,
@ -2604,6 +2631,7 @@ impl<'db> KnownInstanceType<'db> {
"Counter" => Self::Counter,
"ChainMap" => Self::ChainMap,
"OrderedDict" => Self::OrderedDict,
"Generic" => Self::Generic,
"Protocol" => Self::Protocol,
"Optional" => Self::Optional,
"Union" => Self::Union,
@ -2662,6 +2690,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::NoReturn
| Self::Tuple
| Self::Type
| Self::Generic
| Self::Callable => module.is_typing(),
Self::Annotated
| Self::Protocol

View File

@ -1,11 +1,12 @@
use crate::types::{todo_type, ClassType, DynamicType, KnownClass, KnownInstanceType, Type};
use crate::types::{
todo_type, ClassType, DynamicType, KnownClass, KnownInstanceType, MroIterator, Type,
};
use crate::Db;
use itertools::Either;
/// Enumeration of the possible kinds of types we allow in class bases.
///
/// This is much more limited than the [`Type`] enum: all types that would be invalid to have as a
/// class base are transformed into [`ClassBase::unknown`]
/// class base are transformed into [`ClassBase::unknown()`]
///
/// Note that a non-specialized generic class _cannot_ be a class base. When we see a
/// non-specialized generic class in any type expression (including the list of base classes), we
@ -14,6 +15,13 @@ use itertools::Either;
pub enum ClassBase<'db> {
Dynamic(DynamicType),
Class(ClassType<'db>),
/// Although `Protocol` is not a class in typeshed's stubs, it is at runtime,
/// and can appear in the MRO of a class.
Protocol,
/// Bare `Generic` cannot be subclassed directly in user code,
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
/// `Protocol[T]`, or bare `Protocol`.
Generic,
}
impl<'db> ClassBase<'db> {
@ -25,13 +33,6 @@ impl<'db> ClassBase<'db> {
Self::Dynamic(DynamicType::Unknown)
}
pub(crate) const fn is_dynamic(self) -> bool {
match self {
ClassBase::Dynamic(_) => true,
ClassBase::Class(_) => false,
}
}
pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
struct Display<'db> {
base: ClassBase<'db>,
@ -48,6 +49,8 @@ impl<'db> ClassBase<'db> {
ClassBase::Class(ClassType::Generic(alias)) => {
write!(f, "<class '{}'>", alias.display(self.db))
}
ClassBase::Protocol => f.write_str("typing.Protocol"),
ClassBase::Generic => f.write_str("typing.Generic"),
}
}
}
@ -165,7 +168,8 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::Callable => {
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
}
KnownInstanceType::Protocol => Some(ClassBase::Dynamic(DynamicType::TodoProtocol)),
KnownInstanceType::Protocol => Some(ClassBase::Protocol),
KnownInstanceType::Generic => Some(ClassBase::Generic),
},
}
}
@ -173,18 +177,21 @@ impl<'db> ClassBase<'db> {
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
match self {
Self::Class(class) => Some(class),
Self::Dynamic(_) => None,
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
}
}
/// Iterate over the MRO of this base
pub(super) fn mro(
self,
db: &'db dyn Db,
) -> Either<impl Iterator<Item = ClassBase<'db>>, impl Iterator<Item = ClassBase<'db>>> {
pub(super) fn mro(self, db: &'db dyn Db) -> impl Iterator<Item = ClassBase<'db>> {
match self {
ClassBase::Dynamic(_) => Either::Left([self, ClassBase::object(db)].into_iter()),
ClassBase::Class(class) => Either::Right(class.iter_mro(db)),
ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic),
ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => ClassBaseMroIterator::length_3(
db,
self,
ClassBase::Dynamic(DynamicType::SubscriptedGeneric),
),
ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self),
ClassBase::Class(class) => ClassBaseMroIterator::from_class(db, class),
}
}
}
@ -200,6 +207,8 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
match value {
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
ClassBase::Class(class) => class.into(),
ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol),
ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic),
}
}
}
@ -209,3 +218,41 @@ impl<'db> From<&ClassBase<'db>> for Type<'db> {
Self::from(*value)
}
}
/// An iterator over the MRO of a class base.
enum ClassBaseMroIterator<'db> {
Length2(core::array::IntoIter<ClassBase<'db>, 2>),
Length3(core::array::IntoIter<ClassBase<'db>, 3>),
FromClass(MroIterator<'db>),
}
impl<'db> ClassBaseMroIterator<'db> {
/// Iterate over an MRO of length 2 that consists of `first_element` and then `object`.
fn length_2(db: &'db dyn Db, first_element: ClassBase<'db>) -> Self {
ClassBaseMroIterator::Length2([first_element, ClassBase::object(db)].into_iter())
}
/// Iterate over an MRO of length 3 that consists of `first_element`, then `second_element`, then `object`.
fn length_3(db: &'db dyn Db, element_1: ClassBase<'db>, element_2: ClassBase<'db>) -> Self {
ClassBaseMroIterator::Length3([element_1, element_2, ClassBase::object(db)].into_iter())
}
/// Iterate over the MRO of an arbitrary class. The MRO may be of any length.
fn from_class(db: &'db dyn Db, class: ClassType<'db>) -> Self {
ClassBaseMroIterator::FromClass(class.iter_mro(db))
}
}
impl<'db> Iterator for ClassBaseMroIterator<'db> {
type Item = ClassBase<'db>;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Length2(iter) => iter.next(),
Self::Length3(iter) => iter.next(),
Self::FromClass(iter) => iter.next(),
}
}
}
impl std::iter::FusedIterator for ClassBaseMroIterator<'_> {}

View File

@ -7,13 +7,12 @@ use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;
use crate::types::class::{ClassType, GenericAlias, GenericClass};
use crate::types::class_base::ClassBase;
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{
FunctionSignature, InstanceType, IntersectionType, KnownClass, MethodWrapperKind,
StringLiteralType, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
WrapperDescriptorKind,
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
UnionType, WrapperDescriptorKind,
};
use crate::Db;
use rustc_hash::FxHashMap;
@ -92,8 +91,8 @@ impl Display for DisplayRepresentation<'_> {
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
// Only show the bare class name here; ClassBase::display would render this as
// type[<class 'Foo'>] instead of type[Foo].
ClassBase::Class(class) => write!(f, "type[{}]", class.name(self.db)),
ClassBase::Dynamic(dynamic) => write!(f, "type[{dynamic}]"),
SubclassOfInner::Class(class) => write!(f, "type[{}]", class.name(self.db)),
SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"),
},
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
Type::FunctionLiteral(function) => {

View File

@ -94,7 +94,6 @@ use crate::unpack::{Unpack, UnpackPosition};
use crate::util::subscript::{PyIndex, PySlice};
use crate::Db;
use super::class_base::ClassBase;
use super::context::{InNoTypeCheck, InferContext};
use super::diagnostic::{
report_index_out_of_bounds, report_invalid_exception_caught, report_invalid_exception_cause,
@ -107,7 +106,8 @@ use super::slots::check_class_slots;
use super::string_annotation::{
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION,
};
use super::{BoundSuperError, BoundSuperType};
use super::subclass_of::SubclassOfInner;
use super::{BoundSuperError, BoundSuperType, ClassBase};
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
@ -763,12 +763,25 @@ impl<'db> TypeInferenceBuilder<'db> {
continue;
}
// (2) Check for classes that inherit from `@final` classes
// (2) Check for inheritance from plain `Generic`,
// and from classes that inherit from `@final` classes
for (i, base_class) in class.explicit_bases(self.db()).iter().enumerate() {
// dynamic/unknown bases are never `@final`
let Some(base_class) = base_class.into_class_literal() else {
continue;
let base_class = match base_class {
Type::KnownInstance(KnownInstanceType::Generic) => {
// `Generic` can appear in the MRO of many classes,
// but it is never valid as an explicit base class in user code.
self.context.report_lint_old(
&INVALID_BASE,
&class_node.bases()[i],
format_args!("Cannot inherit from plain `Generic`",),
);
continue;
}
Type::ClassLiteral(class) => class,
// dynamic/unknown bases are never `@final`
_ => continue,
};
if !base_class.is_final(self.db()) {
continue;
}
@ -4736,10 +4749,10 @@ impl<'db> TypeInferenceBuilder<'db> {
}
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
match subclass_of.subclass_of() {
ClassBase::Class(class) => {
SubclassOfInner::Class(class) => {
!class.instance_member(db, attr).symbol.is_unbound()
}
ClassBase::Dynamic(_) => unreachable!(
SubclassOfInner::Dynamic(_) => unreachable!(
"Attribute lookup on a dynamic `SubclassOf` type should always return a bound symbol"
),
}
@ -4981,8 +4994,10 @@ impl<'db> TypeInferenceBuilder<'db> {
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
(todo @ Type::Dynamic(DynamicType::Todo(_)), _, _)
| (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo),
(todo @ Type::Dynamic(DynamicType::TodoProtocol), _, _)
| (_, todo @ Type::Dynamic(DynamicType::TodoProtocol), _) => Some(todo),
(todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _, _)
| (_, todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _) => Some(todo),
(todo @ Type::Dynamic(DynamicType::SubscriptedGeneric), _, _)
| (_, todo @ Type::Dynamic(DynamicType::SubscriptedGeneric), _) => Some(todo),
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some(
@ -6224,7 +6239,10 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::IntLiteral(i64::from(bool)),
),
(Type::KnownInstance(KnownInstanceType::Protocol), _) => {
Type::Dynamic(DynamicType::TodoProtocol)
Type::Dynamic(DynamicType::SubscriptedProtocol)
}
(Type::KnownInstance(KnownInstanceType::Generic), _) => {
Type::Dynamic(DynamicType::SubscriptedGeneric)
}
(Type::KnownInstance(known_instance), _)
if known_instance.class().is_special_form() =>
@ -6331,12 +6349,19 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
report_non_subscriptable(
&self.context,
value_node.into(),
value_ty,
"__class_getitem__",
);
// TODO: properly handle old-style generics; get rid of this temporary hack
if !value_ty.into_class_literal().is_some_and(|class| {
class
.iter_mro(self.db(), None)
.contains(&ClassBase::Dynamic(DynamicType::SubscriptedGeneric))
}) {
report_non_subscriptable(
&self.context,
value_node.into(),
value_ty,
"__class_getitem__",
);
}
} else {
report_non_subscriptable(
&self.context,
@ -7508,7 +7533,11 @@ impl<'db> TypeInferenceBuilder<'db> {
}
KnownInstanceType::Protocol => {
self.infer_type_expression(arguments_slice);
Type::Dynamic(DynamicType::TodoProtocol)
Type::Dynamic(DynamicType::SubscriptedProtocol)
}
KnownInstanceType::Generic => {
self.infer_type_expression(arguments_slice);
Type::Dynamic(DynamicType::SubscriptedGeneric)
}
KnownInstanceType::NoReturn
| KnownInstanceType::Never

View File

@ -1,12 +1,12 @@
use crate::symbol::SymbolAndQualifiers;
use super::{ClassBase, Db, KnownClass, MemberLookupPolicy, Type};
use super::{ClassType, Db, DynamicType, KnownClass, MemberLookupPolicy, Type};
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub struct SubclassOfType<'db> {
// Keep this field private, so that the only way of constructing the struct is through the `from` method.
subclass_of: ClassBase<'db>,
subclass_of: SubclassOfInner<'db>,
}
impl<'db> SubclassOfType<'db> {
@ -21,11 +21,11 @@ impl<'db> SubclassOfType<'db> {
///
/// The eager normalization here means that we do not need to worry elsewhere about distinguishing
/// between `@final` classes and other classes when dealing with [`Type::SubclassOf`] variants.
pub(crate) fn from(db: &'db dyn Db, subclass_of: impl Into<ClassBase<'db>>) -> Type<'db> {
pub(crate) fn from(db: &'db dyn Db, subclass_of: impl Into<SubclassOfInner<'db>>) -> Type<'db> {
let subclass_of = subclass_of.into();
match subclass_of {
ClassBase::Dynamic(_) => Type::SubclassOf(Self { subclass_of }),
ClassBase::Class(class) => {
SubclassOfInner::Dynamic(_) => Type::SubclassOf(Self { subclass_of }),
SubclassOfInner::Class(class) => {
if class.is_final(db) {
Type::from(class)
} else if class.is_object(db) {
@ -40,19 +40,19 @@ impl<'db> SubclassOfType<'db> {
/// Return a [`Type`] instance representing the type `type[Unknown]`.
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
Type::SubclassOf(SubclassOfType {
subclass_of: ClassBase::unknown(),
subclass_of: SubclassOfInner::unknown(),
})
}
/// Return a [`Type`] instance representing the type `type[Any]`.
pub(crate) const fn subclass_of_any() -> Type<'db> {
Type::SubclassOf(SubclassOfType {
subclass_of: ClassBase::any(),
subclass_of: SubclassOfInner::Dynamic(DynamicType::Any),
})
}
/// Return the inner [`ClassBase`] value wrapped by this `SubclassOfType`.
pub(crate) const fn subclass_of(self) -> ClassBase<'db> {
/// Return the inner [`SubclassOfInner`] value wrapped by this `SubclassOfType`.
pub(crate) const fn subclass_of(self) -> SubclassOfInner<'db> {
self.subclass_of
}
@ -77,17 +77,17 @@ impl<'db> SubclassOfType<'db> {
/// Return `true` if `self` is a subtype of `other`.
///
/// This can only return `true` if `self.subclass_of` is a [`ClassBase::Class`] variant;
/// This can only return `true` if `self.subclass_of` is a [`SubclassOfInner::Class`] variant;
/// only fully static types participate in subtyping.
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool {
match (self.subclass_of, other.subclass_of) {
// Non-fully-static types do not participate in subtyping
(ClassBase::Dynamic(_), _) | (_, ClassBase::Dynamic(_)) => false,
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => false,
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
// and `type[int]` describes all possible runtime subclasses of the class `int`.
// The first set is a subset of the second set, because `bool` is itself a subclass of `int`.
(ClassBase::Class(self_class), ClassBase::Class(other_class)) => {
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
// N.B. The subclass relation is fully static
self_class.is_subclass_of(db, other_class)
}
@ -96,8 +96,73 @@ impl<'db> SubclassOfType<'db> {
pub(crate) fn to_instance(self) -> Type<'db> {
match self.subclass_of {
ClassBase::Class(class) => Type::instance(class),
ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
SubclassOfInner::Class(class) => Type::instance(class),
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
}
}
}
/// An enumeration of the different kinds of `type[]` types that a [`SubclassOfType`] can represent:
///
/// 1. A "subclass of a class": `type[C]` for any class object `C`
/// 2. A "subclass of a dynamic type": `type[Any]`, `type[Unknown]` and `type[@Todo]`
///
/// In the long term, we may want to implement <https://github.com/astral-sh/ruff/issues/15381>.
/// Doing this would allow us to get rid of this enum,
/// since `type[Any]` would be represented as `type & Any`
/// rather than using the [`Type::SubclassOf`] variant at all;
/// [`SubclassOfType`] would then be a simple wrapper around [`ClassType`].
///
/// Note that this enum is similar to the [`super::ClassBase`] enum,
/// but does not include the `ClassBase::Protocol` and `ClassBase::Generic` variants
/// (`type[Protocol]` and `type[Generic]` are not valid types).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub(crate) enum SubclassOfInner<'db> {
Class(ClassType<'db>),
Dynamic(DynamicType),
}
impl<'db> SubclassOfInner<'db> {
pub(crate) const fn unknown() -> Self {
Self::Dynamic(DynamicType::Unknown)
}
pub(crate) const fn is_dynamic(self) -> bool {
matches!(self, Self::Dynamic(_))
}
pub(crate) const fn into_class(self) -> Option<ClassType<'db>> {
match self {
Self::Class(class) => Some(class),
Self::Dynamic(_) => None,
}
}
pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
match ty {
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
Type::ClassLiteral(literal) => Some(if literal.is_known(db, KnownClass::Any) {
Self::Dynamic(DynamicType::Any)
} else {
Self::Class(literal.default_specialization(db))
}),
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
_ => None,
}
}
}
impl<'db> From<ClassType<'db>> for SubclassOfInner<'db> {
fn from(value: ClassType<'db>) -> Self {
SubclassOfInner::Class(value)
}
}
impl<'db> From<SubclassOfInner<'db>> for Type<'db> {
fn from(value: SubclassOfInner<'db>) -> Self {
match value {
SubclassOfInner::Dynamic(dynamic) => Type::Dynamic(dynamic),
SubclassOfInner::Class(class) => class.into(),
}
}
}

View File

@ -3,8 +3,8 @@ use std::cmp::Ordering;
use crate::db::Db;
use super::{
class_base::ClassBase, DynamicType, InstanceType, KnownInstanceType, SuperOwnerKind, TodoType,
Type,
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, InstanceType,
KnownInstanceType, SuperOwnerKind, TodoType, Type,
};
/// Return an [`Ordering`] that describes the canonical order in which two types should appear
@ -109,10 +109,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::SubclassOf(left), Type::SubclassOf(right)) => {
match (left.subclass_of(), right.subclass_of()) {
(ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right),
(ClassBase::Class(_), _) => Ordering::Less,
(_, ClassBase::Class(_)) => Ordering::Greater,
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
(SubclassOfInner::Class(left), SubclassOfInner::Class(right)) => left.cmp(&right),
(SubclassOfInner::Class(_), _) => Ordering::Less,
(_, SubclassOfInner::Class(_)) => Ordering::Greater,
(SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}
}
@ -143,6 +143,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(right),
(ClassBase::Class(_), _) => Ordering::Less,
(_, ClassBase::Class(_)) => Ordering::Greater,
(ClassBase::Protocol, _) => Ordering::Less,
(_, ClassBase::Protocol) => Ordering::Greater,
(ClassBase::Generic, _) => Ordering::Less,
(_, ClassBase::Generic) => Ordering::Greater,
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
dynamic_elements_ordering(*left, *right)
}
@ -230,6 +234,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(KnownInstanceType::OrderedDict, _) => Ordering::Less,
(_, KnownInstanceType::OrderedDict) => Ordering::Greater,
(KnownInstanceType::Generic, _) => Ordering::Less,
(_, KnownInstanceType::Generic) => Ordering::Greater,
(KnownInstanceType::Protocol, _) => Ordering::Less,
(_, KnownInstanceType::Protocol) => Ordering::Greater,
@ -364,7 +371,10 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
#[cfg(not(debug_assertions))]
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
(DynamicType::TodoProtocol, _) => Ordering::Less,
(_, DynamicType::TodoProtocol) => Ordering::Greater,
(DynamicType::SubscriptedGeneric, _) => Ordering::Less,
(_, DynamicType::SubscriptedGeneric) => Ordering::Greater,
(DynamicType::SubscriptedProtocol, _) => Ordering::Less,
(_, DynamicType::SubscriptedProtocol) => Ordering::Greater,
}
}