mirror of https://github.com/astral-sh/ruff
[red-knot] Fix MRO inference for protocol classes; allow inheritance from subscripted `Generic[]`; forbid subclassing unsubscripted `Generic` (#17452)
This commit is contained in:
parent
fd3fc34a9e
commit
454ad15aee
|
|
@ -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): ...
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ from typing import Generic, TypeVar
|
|||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-base]
|
||||
class C(Generic[T]): ...
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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]): ...
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<'_> {}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue