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
|
### Built-ins
|
||||||
|
####################
|
||||||
|
|
||||||
class ListSubclass(typing.List): ...
|
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__)
|
reveal_type(ListSubclass.__mro__)
|
||||||
|
|
||||||
class DictSubclass(typing.Dict): ...
|
class DictSubclass(typing.Dict): ...
|
||||||
|
|
||||||
# TODO: should have `Generic`, should not have `Unknown`
|
# TODO: generic protocols
|
||||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]]
|
# 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__)
|
reveal_type(DictSubclass.__mro__)
|
||||||
|
|
||||||
class SetSubclass(typing.Set): ...
|
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__)
|
reveal_type(SetSubclass.__mro__)
|
||||||
|
|
||||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||||
|
|
@ -92,11 +95,12 @@ reveal_type(FrozenSetSubclass.__mro__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
### `collections`
|
### `collections`
|
||||||
|
####################
|
||||||
|
|
||||||
class ChainMapSubclass(typing.ChainMap): ...
|
class ChainMapSubclass(typing.ChainMap): ...
|
||||||
|
|
||||||
# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
# TODO: generic protocols
|
||||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]]
|
# 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__)
|
reveal_type(ChainMapSubclass.__mro__)
|
||||||
|
|
||||||
class CounterSubclass(typing.Counter): ...
|
class CounterSubclass(typing.Counter): ...
|
||||||
|
|
@ -113,7 +117,8 @@ reveal_type(DefaultDictSubclass.__mro__)
|
||||||
|
|
||||||
class DequeSubclass(typing.Deque): ...
|
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__)
|
reveal_type(DequeSubclass.__mro__)
|
||||||
|
|
||||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
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.
|
One thing that is supported is error messages for using special forms in type expressions.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
|
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, Generic
|
||||||
|
|
||||||
def _(
|
def _(
|
||||||
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
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"
|
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"
|
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
|
||||||
e: ParamSpec,
|
e: ParamSpec,
|
||||||
|
f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions"
|
||||||
) -> None:
|
) -> None:
|
||||||
reveal_type(a) # revealed: Unknown
|
reveal_type(a) # revealed: Unknown
|
||||||
reveal_type(b) # 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
|
```py
|
||||||
from typing import Callable
|
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 A(Self): ... # error: [invalid-base]
|
||||||
class B(Unpack): ... # 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 D(TypeIs): ... # error: [invalid-base]
|
||||||
class E(Concatenate): ... # error: [invalid-base]
|
class E(Concatenate): ... # error: [invalid-base]
|
||||||
class F(Callable): ...
|
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]]
|
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")
|
T = TypeVar("T")
|
||||||
|
|
||||||
# TODO: no error
|
|
||||||
# error: [invalid-base]
|
|
||||||
class C(Generic[T]): ...
|
class C(Generic[T]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,6 @@ from typing import TypeVar, Generic
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
S = TypeVar("S")
|
S = TypeVar("S")
|
||||||
|
|
||||||
# TODO: no error
|
|
||||||
# error: [invalid-base]
|
|
||||||
class Legacy(Generic[T]):
|
class Legacy(Generic[T]):
|
||||||
def m(self, x: T, y: S) -> S:
|
def m(self, x: T, y: S) -> S:
|
||||||
return y
|
return y
|
||||||
|
|
@ -177,8 +175,6 @@ def f(x: T) -> None:
|
||||||
# TODO: invalid-assignment error
|
# TODO: invalid-assignment error
|
||||||
y: list[S] = []
|
y: list[S] = []
|
||||||
|
|
||||||
# TODO: no error
|
|
||||||
# error: [invalid-base]
|
|
||||||
class C(Generic[T]):
|
class C(Generic[T]):
|
||||||
# TODO: error: cannot use S if it's not in the current generic context
|
# TODO: error: cannot use S if it's not in the current generic context
|
||||||
x: list[S] = []
|
x: list[S] = []
|
||||||
|
|
@ -259,8 +255,7 @@ def f[T](x: T, y: T) -> None:
|
||||||
class Ok[S]: ...
|
class Ok[S]: ...
|
||||||
# TODO: error for reuse of typevar
|
# TODO: error for reuse of typevar
|
||||||
class Bad1[T]: ...
|
class Bad1[T]: ...
|
||||||
# TODO: no non-subscriptable error, error for reuse of typevar
|
# TODO: error for reuse of typevar
|
||||||
# error: [non-subscriptable]
|
|
||||||
class Bad2(Iterable[T]): ...
|
class Bad2(Iterable[T]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -273,8 +268,7 @@ class C[T]:
|
||||||
class Ok1[S]: ...
|
class Ok1[S]: ...
|
||||||
# TODO: error for reuse of typevar
|
# TODO: error for reuse of typevar
|
||||||
class Bad1[T]: ...
|
class Bad1[T]: ...
|
||||||
# TODO: no non-subscriptable error, error for reuse of typevar
|
# TODO: error for reuse of typevar
|
||||||
# error: [non-subscriptable]
|
|
||||||
class Bad2(Iterable[T]): ...
|
class Bad2(Iterable[T]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ from typing import Protocol
|
||||||
|
|
||||||
class MyProtocol(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], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], @Todo(protocol), Literal[object]]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
|
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
|
```py
|
||||||
class SubclassOfMyProtocol(MyProtocol): ...
|
class SubclassOfMyProtocol(MyProtocol): ...
|
||||||
|
|
||||||
# TODO
|
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], @Todo(protocol), Literal[object]]
|
|
||||||
reveal_type(SubclassOfMyProtocol.__mro__)
|
reveal_type(SubclassOfMyProtocol.__mro__)
|
||||||
|
|
||||||
# TODO: should be `Literal[False]`
|
# TODO: should be `Literal[False]`
|
||||||
|
|
@ -94,8 +92,7 @@ class OtherProtocol(Protocol):
|
||||||
|
|
||||||
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
|
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
|
||||||
|
|
||||||
# TODO
|
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[object]]
|
|
||||||
reveal_type(ComplexInheritance.__mro__)
|
reveal_type(ComplexInheritance.__mro__)
|
||||||
|
|
||||||
# TODO: should be `Literal[True]`
|
# 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]`
|
# TODO: should emit `[invalid-protocol]`
|
||||||
class Invalid(NotAProtocol, Protocol): ...
|
class Invalid(NotAProtocol, Protocol): ...
|
||||||
|
|
||||||
# TODO
|
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
|
|
||||||
reveal_type(Invalid.__mro__)
|
reveal_type(Invalid.__mro__)
|
||||||
|
|
||||||
# TODO: should emit an `[invalid-protocol`] error
|
# TODO: should emit an `[invalid-protocol`] error
|
||||||
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
|
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
|
||||||
|
|
||||||
# TODO
|
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
|
|
||||||
reveal_type(AlsoInvalid.__mro__)
|
reveal_type(AlsoInvalid.__mro__)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -135,11 +130,9 @@ T = TypeVar("T")
|
||||||
# type checkers.
|
# type checkers.
|
||||||
class Fine(Protocol, object): ...
|
class Fine(Protocol, object): ...
|
||||||
|
|
||||||
# TODO
|
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], typing.Protocol, typing.Generic, Literal[object]]
|
||||||
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], @Todo(protocol), Literal[object]]
|
|
||||||
|
|
||||||
# TODO: should not error
|
class StillFine(Protocol, Generic[T], object): ...
|
||||||
class StillFine(Protocol, Generic[T], object): ... # error: [invalid-base]
|
|
||||||
class EvenThis[T](Protocol, 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
|
```py
|
||||||
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
|
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
|
||||||
|
|
||||||
# TODO
|
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[NotAProtocol], Literal[object]]
|
||||||
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[NotAProtocol], Literal[object]]
|
|
||||||
reveal_type(FineAndDandy.__mro__)
|
reveal_type(FineAndDandy.__mro__)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ from typing import Tuple
|
||||||
|
|
||||||
class C(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__)
|
reveal_type(C.__mro__)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub(crate) use self::infer::{
|
||||||
};
|
};
|
||||||
pub(crate) use self::narrow::KnownConstraintFunction;
|
pub(crate) use self::narrow::KnownConstraintFunction;
|
||||||
pub(crate) use self::signatures::{CallableSignature, Signature, Signatures};
|
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_name::ModuleName;
|
||||||
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
||||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId};
|
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 {
|
pub fn contains_todo(&self, db: &'db dyn Db) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
|
Self::Dynamic(
|
||||||
|
DynamicType::Todo(_)
|
||||||
|
| DynamicType::SubscriptedProtocol
|
||||||
|
| DynamicType::SubscriptedGeneric,
|
||||||
|
) => true,
|
||||||
|
|
||||||
Self::AlwaysFalsy
|
Self::AlwaysFalsy
|
||||||
| Self::AlwaysTruthy
|
| Self::AlwaysTruthy
|
||||||
|
|
@ -540,9 +544,13 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
Self::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
|
||||||
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol) => true,
|
SubclassOfInner::Dynamic(
|
||||||
ClassBase::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
|
DynamicType::Todo(_)
|
||||||
ClassBase::Class(_) => false,
|
| DynamicType::SubscriptedProtocol
|
||||||
|
| DynamicType::SubscriptedGeneric,
|
||||||
|
) => true,
|
||||||
|
SubclassOfInner::Dynamic(DynamicType::Unknown | DynamicType::Any) => false,
|
||||||
|
SubclassOfInner::Class(_) => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||||
|
|
@ -557,10 +565,18 @@ impl<'db> Type<'db> {
|
||||||
Self::BoundSuper(bound_super) => {
|
Self::BoundSuper(bound_super) => {
|
||||||
matches!(
|
matches!(
|
||||||
bound_super.pivot_class(db),
|
bound_super.pivot_class(db),
|
||||||
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoProtocol)
|
ClassBase::Dynamic(
|
||||||
|
DynamicType::Todo(_)
|
||||||
|
| DynamicType::SubscriptedGeneric
|
||||||
|
| DynamicType::SubscriptedProtocol
|
||||||
|
)
|
||||||
) || matches!(
|
) || matches!(
|
||||||
bound_super.owner(db),
|
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)) => {
|
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
||||||
match (first.subclass_of(), second.subclass_of()) {
|
match (first.subclass_of(), second.subclass_of()) {
|
||||||
(first, second) if first == second => true,
|
(first, second) if first == second => true,
|
||||||
(ClassBase::Dynamic(_), ClassBase::Dynamic(_)) => true,
|
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1633,16 +1649,16 @@ impl<'db> Type<'db> {
|
||||||
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
||||||
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||||
match subclass_of_ty.subclass_of() {
|
match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Dynamic(_) => false,
|
SubclassOfInner::Dynamic(_) => false,
|
||||||
ClassBase::Class(class_a) => !class_b.is_subclass_of(db, None, class_a),
|
SubclassOfInner::Class(class_a) => !class_b.is_subclass_of(db, None, class_a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
|
(Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
|
||||||
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
|
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||||
match subclass_of_ty.subclass_of() {
|
match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Dynamic(_) => false,
|
SubclassOfInner::Dynamic(_) => false,
|
||||||
ClassBase::Class(class_a) => {
|
SubclassOfInner::Class(class_a) => {
|
||||||
!ClassType::from(alias_b).is_subclass_of(db, 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
|
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||||
(Type::SubclassOf(subclass_of_ty), other)
|
(Type::SubclassOf(subclass_of_ty), other)
|
||||||
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
|
| (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)
|
KnownClass::Type.to_instance(db).is_disjoint_from(db, other)
|
||||||
}
|
}
|
||||||
ClassBase::Class(class) => class
|
SubclassOfInner::Class(class) => class
|
||||||
.metaclass_instance_type(db)
|
.metaclass_instance_type(db)
|
||||||
.is_disjoint_from(db, other),
|
.is_disjoint_from(db, other),
|
||||||
},
|
},
|
||||||
|
|
@ -3073,8 +3089,8 @@ impl<'db> Type<'db> {
|
||||||
.try_bool_impl(db, allow_short_circuit)?,
|
.try_bool_impl(db, allow_short_circuit)?,
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Dynamic(_) => Truthiness::Ambiguous,
|
SubclassOfInner::Dynamic(_) => Truthiness::Ambiguous,
|
||||||
ClassBase::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
Type::from(class).try_bool_impl(db, allow_short_circuit)?
|
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() {
|
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
|
// 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.
|
// getting the signature here. This signature can still be used in some cases (e.g.
|
||||||
// evaluating callable subtyping). TODO improve this definition (intersection of
|
// evaluating callable subtyping). TODO improve this definition (intersection of
|
||||||
// `__new__` and `__init__` signatures? and respect metaclass `__call__`).
|
// `__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(_) => {
|
Type::Instance(_) => {
|
||||||
|
|
@ -4380,6 +4398,10 @@ impl<'db> Type<'db> {
|
||||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
|
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
|
||||||
fallback_type: Type::unknown(),
|
fallback_type: Type::unknown(),
|
||||||
}),
|
}),
|
||||||
|
KnownInstanceType::Generic => Err(InvalidTypeExpressionError {
|
||||||
|
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic],
|
||||||
|
fallback_type: Type::unknown(),
|
||||||
|
}),
|
||||||
|
|
||||||
KnownInstanceType::Literal
|
KnownInstanceType::Literal
|
||||||
| KnownInstanceType::Union
|
| KnownInstanceType::Union
|
||||||
|
|
@ -4564,21 +4586,21 @@ impl<'db> Type<'db> {
|
||||||
Type::ClassLiteral(class) => class.metaclass(db),
|
Type::ClassLiteral(class) => class.metaclass(db),
|
||||||
Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db),
|
Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db),
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Dynamic(_) => *self,
|
SubclassOfInner::Dynamic(_) => *self,
|
||||||
ClassBase::Class(class) => SubclassOfType::from(
|
SubclassOfInner::Class(class) => SubclassOfType::from(
|
||||||
db,
|
db,
|
||||||
ClassBase::try_from_type(db, class.metaclass(db))
|
SubclassOfInner::try_from_type(db, class.metaclass(db))
|
||||||
.unwrap_or(ClassBase::unknown()),
|
.unwrap_or(SubclassOfInner::unknown()),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
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
|
// TODO intersections
|
||||||
Type::Intersection(_) => SubclassOfType::from(
|
Type::Intersection(_) => SubclassOfType::from(
|
||||||
db,
|
db,
|
||||||
ClassBase::try_from_type(db, todo_type!("Intersection meta-type"))
|
SubclassOfInner::try_from_type(db, todo_type!("Intersection meta-type"))
|
||||||
.expect("Type::Todo should be a valid ClassBase"),
|
.expect("Type::Todo should be a valid `SubclassOfInner`"),
|
||||||
),
|
),
|
||||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||||
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(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() {
|
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||||
ClassBase::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||||
ClassBase::Dynamic(_) => None,
|
SubclassOfInner::Dynamic(_) => None,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::StringLiteral(_)
|
Self::StringLiteral(_)
|
||||||
|
|
@ -4833,9 +4855,12 @@ pub enum DynamicType {
|
||||||
///
|
///
|
||||||
/// This variant should be created with the `todo_type!` macro.
|
/// This variant should be created with the `todo_type!` macro.
|
||||||
Todo(TodoType),
|
Todo(TodoType),
|
||||||
/// Temporary type until we support protocols. We use a separate variant (instead of `Todo(…)`)
|
/// Temporary type until we support generic protocols.
|
||||||
/// in order to be able to match on them explicitly.
|
/// We use a separate variant (instead of `Todo(…)`) in order to be able to match on them explicitly.
|
||||||
TodoProtocol,
|
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 {
|
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
|
// `DynamicType::Todo`'s display should be explicit that is not a valid display of
|
||||||
// any other type
|
// any other type
|
||||||
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
|
DynamicType::Todo(todo) => write!(f, "@Todo{todo}"),
|
||||||
DynamicType::TodoProtocol => f.write_str(if cfg!(debug_assertions) {
|
DynamicType::SubscriptedProtocol => f.write_str(if cfg!(debug_assertions) {
|
||||||
"@Todo(protocol)"
|
"@Todo(`Protocol[]` subscript)"
|
||||||
|
} else {
|
||||||
|
"@Todo"
|
||||||
|
}),
|
||||||
|
DynamicType::SubscriptedGeneric => f.write_str(if cfg!(debug_assertions) {
|
||||||
|
"@Todo(`Generic[]` subscript)"
|
||||||
} else {
|
} else {
|
||||||
"@Todo"
|
"@Todo"
|
||||||
}),
|
}),
|
||||||
|
|
@ -4959,8 +4989,10 @@ enum InvalidTypeExpression<'db> {
|
||||||
RequiresArguments(Type<'db>),
|
RequiresArguments(Type<'db>),
|
||||||
/// Some types always require at least two arguments when used in a type expression
|
/// Some types always require at least two arguments when used in a type expression
|
||||||
RequiresTwoArguments(Type<'db>),
|
RequiresTwoArguments(Type<'db>),
|
||||||
/// The `Protocol` type is invalid in type expressions
|
/// The `Protocol` class is invalid in type expressions
|
||||||
Protocol,
|
Protocol,
|
||||||
|
/// Same for `Generic`
|
||||||
|
Generic,
|
||||||
/// Type qualifiers are always invalid in *type expressions*,
|
/// Type qualifiers are always invalid in *type expressions*,
|
||||||
/// but these ones are okay with 0 arguments in *annotation expressions*
|
/// but these ones are okay with 0 arguments in *annotation expressions*
|
||||||
TypeQualifier(KnownInstanceType<'db>),
|
TypeQualifier(KnownInstanceType<'db>),
|
||||||
|
|
@ -4999,6 +5031,9 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||||
InvalidTypeExpression::Protocol => f.write_str(
|
InvalidTypeExpression::Protocol => f.write_str(
|
||||||
"`typing.Protocol` is not allowed in type expressions"
|
"`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!(
|
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
||||||
f,
|
f,
|
||||||
"Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)",
|
"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.
|
/// 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
|
/// [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);
|
let (class_literal, specialization) = self.class_literal(db);
|
||||||
class_literal.iter_mro(db, specialization)
|
class_literal.iter_mro(db, specialization)
|
||||||
}
|
}
|
||||||
|
|
@ -645,7 +645,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Option<Specialization<'db>>,
|
specialization: Option<Specialization<'db>>,
|
||||||
) -> impl Iterator<Item = ClassBase<'db>> {
|
) -> MroIterator<'db> {
|
||||||
MroIterator::new(db, self, specialization)
|
MroIterator::new(db, self, specialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -846,7 +846,11 @@ impl<'db> ClassLiteralType<'db> {
|
||||||
|
|
||||||
for superclass in mro_iter {
|
for superclass in mro_iter {
|
||||||
match superclass {
|
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
|
// 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
|
// avoid creating many dynamic types in our test suite that would otherwise
|
||||||
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
|
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
|
||||||
|
|
@ -865,7 +869,12 @@ impl<'db> ClassLiteralType<'db> {
|
||||||
continue;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1158,8 +1167,12 @@ impl<'db> ClassLiteralType<'db> {
|
||||||
|
|
||||||
for superclass in self.iter_mro(db, specialization) {
|
for superclass in self.iter_mro(db, specialization) {
|
||||||
match superclass {
|
match superclass {
|
||||||
ClassBase::Dynamic(DynamicType::TodoProtocol) => {
|
ClassBase::Dynamic(
|
||||||
// TODO: We currently skip `Protocol` when looking up instance members, in order to
|
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
|
// avoid creating many dynamic types in our test suite that would otherwise
|
||||||
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
|
// result from looking up attributes on builtin types like `str`, `list`, `tuple`
|
||||||
}
|
}
|
||||||
|
|
@ -1671,6 +1684,8 @@ pub(crate) enum KnownClass {
|
||||||
Super,
|
Super,
|
||||||
// enum
|
// enum
|
||||||
Enum,
|
Enum,
|
||||||
|
// abc
|
||||||
|
ABCMeta,
|
||||||
// Types
|
// Types
|
||||||
GenericAlias,
|
GenericAlias,
|
||||||
ModuleType,
|
ModuleType,
|
||||||
|
|
@ -1780,6 +1795,7 @@ impl<'db> KnownClass {
|
||||||
| Self::Float
|
| Self::Float
|
||||||
| Self::Sized
|
| Self::Sized
|
||||||
| Self::Enum
|
| Self::Enum
|
||||||
|
| Self::ABCMeta
|
||||||
// Evaluating `NotImplementedType` in a boolean context was deprecated in Python 3.9
|
// Evaluating `NotImplementedType` in a boolean context was deprecated in Python 3.9
|
||||||
// and raises a `TypeError` in Python >=3.14
|
// and raises a `TypeError` in Python >=3.14
|
||||||
// (see https://docs.python.org/3/library/constants.html#NotImplemented)
|
// (see https://docs.python.org/3/library/constants.html#NotImplemented)
|
||||||
|
|
@ -1836,6 +1852,7 @@ impl<'db> KnownClass {
|
||||||
Self::Sized => "Sized",
|
Self::Sized => "Sized",
|
||||||
Self::OrderedDict => "OrderedDict",
|
Self::OrderedDict => "OrderedDict",
|
||||||
Self::Enum => "Enum",
|
Self::Enum => "Enum",
|
||||||
|
Self::ABCMeta => "ABCMeta",
|
||||||
Self::Super => "super",
|
Self::Super => "super",
|
||||||
// For example, `typing.List` is defined as `List = _Alias()` in typeshed
|
// For example, `typing.List` is defined as `List = _Alias()` in typeshed
|
||||||
Self::StdlibAlias => "_Alias",
|
Self::StdlibAlias => "_Alias",
|
||||||
|
|
@ -1992,6 +2009,7 @@ impl<'db> KnownClass {
|
||||||
| Self::Super
|
| Self::Super
|
||||||
| Self::Property => KnownModule::Builtins,
|
| Self::Property => KnownModule::Builtins,
|
||||||
Self::VersionInfo => KnownModule::Sys,
|
Self::VersionInfo => KnownModule::Sys,
|
||||||
|
Self::ABCMeta => KnownModule::Abc,
|
||||||
Self::Enum => KnownModule::Enum,
|
Self::Enum => KnownModule::Enum,
|
||||||
Self::GenericAlias
|
Self::GenericAlias
|
||||||
| Self::ModuleType
|
| Self::ModuleType
|
||||||
|
|
@ -2096,6 +2114,7 @@ impl<'db> KnownClass {
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
| Self::Sized
|
| Self::Sized
|
||||||
| Self::Enum
|
| Self::Enum
|
||||||
|
| Self::ABCMeta
|
||||||
| Self::Super
|
| Self::Super
|
||||||
| Self::NewType => false,
|
| Self::NewType => false,
|
||||||
}
|
}
|
||||||
|
|
@ -2155,6 +2174,7 @@ impl<'db> KnownClass {
|
||||||
| Self::TypeVarTuple
|
| Self::TypeVarTuple
|
||||||
| Self::Sized
|
| Self::Sized
|
||||||
| Self::Enum
|
| Self::Enum
|
||||||
|
| Self::ABCMeta
|
||||||
| Self::Super
|
| Self::Super
|
||||||
| Self::UnionType
|
| Self::UnionType
|
||||||
| Self::NewType => false,
|
| Self::NewType => false,
|
||||||
|
|
@ -2216,6 +2236,7 @@ impl<'db> KnownClass {
|
||||||
"SupportsIndex" => Self::SupportsIndex,
|
"SupportsIndex" => Self::SupportsIndex,
|
||||||
"Sized" => Self::Sized,
|
"Sized" => Self::Sized,
|
||||||
"Enum" => Self::Enum,
|
"Enum" => Self::Enum,
|
||||||
|
"ABCMeta" => Self::ABCMeta,
|
||||||
"super" => Self::Super,
|
"super" => Self::Super,
|
||||||
"_version_info" => Self::VersionInfo,
|
"_version_info" => Self::VersionInfo,
|
||||||
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
|
"ellipsis" if Program::get(db).python_version(db) <= PythonVersion::PY39 => {
|
||||||
|
|
@ -2271,6 +2292,7 @@ impl<'db> KnownClass {
|
||||||
| Self::MethodType
|
| Self::MethodType
|
||||||
| Self::MethodWrapperType
|
| Self::MethodWrapperType
|
||||||
| Self::Enum
|
| Self::Enum
|
||||||
|
| Self::ABCMeta
|
||||||
| Self::Super
|
| Self::Super
|
||||||
| Self::NotImplementedType
|
| Self::NotImplementedType
|
||||||
| Self::UnionType
|
| Self::UnionType
|
||||||
|
|
@ -2393,6 +2415,8 @@ pub enum KnownInstanceType<'db> {
|
||||||
OrderedDict,
|
OrderedDict,
|
||||||
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
|
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
|
||||||
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`)
|
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
|
||||||
Type,
|
Type,
|
||||||
/// A single instance of `typing.TypeVar`
|
/// A single instance of `typing.TypeVar`
|
||||||
|
|
@ -2467,6 +2491,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
| Self::ChainMap
|
| Self::ChainMap
|
||||||
| Self::OrderedDict
|
| Self::OrderedDict
|
||||||
| Self::Protocol
|
| Self::Protocol
|
||||||
|
| Self::Generic
|
||||||
| Self::ReadOnly
|
| Self::ReadOnly
|
||||||
| Self::TypeAliasType(_)
|
| Self::TypeAliasType(_)
|
||||||
| Self::Unknown
|
| Self::Unknown
|
||||||
|
|
@ -2513,6 +2538,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
Self::ChainMap => "typing.ChainMap",
|
Self::ChainMap => "typing.ChainMap",
|
||||||
Self::OrderedDict => "typing.OrderedDict",
|
Self::OrderedDict => "typing.OrderedDict",
|
||||||
Self::Protocol => "typing.Protocol",
|
Self::Protocol => "typing.Protocol",
|
||||||
|
Self::Generic => "typing.Generic",
|
||||||
Self::ReadOnly => "typing.ReadOnly",
|
Self::ReadOnly => "typing.ReadOnly",
|
||||||
Self::TypeVar(typevar) => typevar.name(db),
|
Self::TypeVar(typevar) => typevar.name(db),
|
||||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||||
|
|
@ -2560,7 +2586,8 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
Self::Deque => KnownClass::StdlibAlias,
|
Self::Deque => KnownClass::StdlibAlias,
|
||||||
Self::ChainMap => KnownClass::StdlibAlias,
|
Self::ChainMap => KnownClass::StdlibAlias,
|
||||||
Self::OrderedDict => 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::TypeVar(_) => KnownClass::TypeVar,
|
||||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||||
Self::TypeOf => KnownClass::SpecialForm,
|
Self::TypeOf => KnownClass::SpecialForm,
|
||||||
|
|
@ -2604,6 +2631,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
"Counter" => Self::Counter,
|
"Counter" => Self::Counter,
|
||||||
"ChainMap" => Self::ChainMap,
|
"ChainMap" => Self::ChainMap,
|
||||||
"OrderedDict" => Self::OrderedDict,
|
"OrderedDict" => Self::OrderedDict,
|
||||||
|
"Generic" => Self::Generic,
|
||||||
"Protocol" => Self::Protocol,
|
"Protocol" => Self::Protocol,
|
||||||
"Optional" => Self::Optional,
|
"Optional" => Self::Optional,
|
||||||
"Union" => Self::Union,
|
"Union" => Self::Union,
|
||||||
|
|
@ -2662,6 +2690,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
| Self::NoReturn
|
| Self::NoReturn
|
||||||
| Self::Tuple
|
| Self::Tuple
|
||||||
| Self::Type
|
| Self::Type
|
||||||
|
| Self::Generic
|
||||||
| Self::Callable => module.is_typing(),
|
| Self::Callable => module.is_typing(),
|
||||||
Self::Annotated
|
Self::Annotated
|
||||||
| Self::Protocol
|
| 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 crate::Db;
|
||||||
use itertools::Either;
|
|
||||||
|
|
||||||
/// Enumeration of the possible kinds of types we allow in class bases.
|
/// 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
|
/// 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
|
/// 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
|
/// 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> {
|
pub enum ClassBase<'db> {
|
||||||
Dynamic(DynamicType),
|
Dynamic(DynamicType),
|
||||||
Class(ClassType<'db>),
|
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> {
|
impl<'db> ClassBase<'db> {
|
||||||
|
|
@ -25,13 +33,6 @@ impl<'db> ClassBase<'db> {
|
||||||
Self::Dynamic(DynamicType::Unknown)
|
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 {
|
pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||||
struct Display<'db> {
|
struct Display<'db> {
|
||||||
base: ClassBase<'db>,
|
base: ClassBase<'db>,
|
||||||
|
|
@ -48,6 +49,8 @@ impl<'db> ClassBase<'db> {
|
||||||
ClassBase::Class(ClassType::Generic(alias)) => {
|
ClassBase::Class(ClassType::Generic(alias)) => {
|
||||||
write!(f, "<class '{}'>", alias.display(self.db))
|
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 => {
|
KnownInstanceType::Callable => {
|
||||||
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
|
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>> {
|
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => Some(class),
|
Self::Class(class) => Some(class),
|
||||||
Self::Dynamic(_) => None,
|
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the MRO of this base
|
/// Iterate over the MRO of this base
|
||||||
pub(super) fn mro(
|
pub(super) fn mro(self, db: &'db dyn Db) -> impl Iterator<Item = ClassBase<'db>> {
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
) -> Either<impl Iterator<Item = ClassBase<'db>>, impl Iterator<Item = ClassBase<'db>>> {
|
|
||||||
match self {
|
match self {
|
||||||
ClassBase::Dynamic(_) => Either::Left([self, ClassBase::object(db)].into_iter()),
|
ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic),
|
||||||
ClassBase::Class(class) => Either::Right(class.iter_mro(db)),
|
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 {
|
match value {
|
||||||
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||||
ClassBase::Class(class) => class.into(),
|
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)
|
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 ruff_python_literal::escape::AsciiEscape;
|
||||||
|
|
||||||
use crate::types::class::{ClassType, GenericAlias, GenericClass};
|
use crate::types::class::{ClassType, GenericAlias, GenericClass};
|
||||||
use crate::types::class_base::ClassBase;
|
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization};
|
||||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
FunctionSignature, InstanceType, IntersectionType, KnownClass, MethodWrapperKind,
|
FunctionSignature, InstanceType, IntersectionType, KnownClass, MethodWrapperKind,
|
||||||
StringLiteralType, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
|
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||||
WrapperDescriptorKind,
|
UnionType, WrapperDescriptorKind,
|
||||||
};
|
};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
@ -92,8 +91,8 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
// Only show the bare class name here; ClassBase::display would render this as
|
// Only show the bare class name here; ClassBase::display would render this as
|
||||||
// type[<class 'Foo'>] instead of type[Foo].
|
// type[<class 'Foo'>] instead of type[Foo].
|
||||||
ClassBase::Class(class) => write!(f, "type[{}]", class.name(self.db)),
|
SubclassOfInner::Class(class) => write!(f, "type[{}]", class.name(self.db)),
|
||||||
ClassBase::Dynamic(dynamic) => write!(f, "type[{dynamic}]"),
|
SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"),
|
||||||
},
|
},
|
||||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
||||||
Type::FunctionLiteral(function) => {
|
Type::FunctionLiteral(function) => {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ use crate::unpack::{Unpack, UnpackPosition};
|
||||||
use crate::util::subscript::{PyIndex, PySlice};
|
use crate::util::subscript::{PyIndex, PySlice};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
use super::class_base::ClassBase;
|
|
||||||
use super::context::{InNoTypeCheck, InferContext};
|
use super::context::{InNoTypeCheck, InferContext};
|
||||||
use super::diagnostic::{
|
use super::diagnostic::{
|
||||||
report_index_out_of_bounds, report_invalid_exception_caught, report_invalid_exception_cause,
|
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::{
|
use super::string_annotation::{
|
||||||
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_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.
|
/// 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
|
/// 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;
|
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() {
|
for (i, base_class) in class.explicit_bases(self.db()).iter().enumerate() {
|
||||||
// dynamic/unknown bases are never `@final`
|
let base_class = match base_class {
|
||||||
let Some(base_class) = base_class.into_class_literal() else {
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
Type::ClassLiteral(class) => class,
|
||||||
|
// dynamic/unknown bases are never `@final`
|
||||||
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !base_class.is_final(self.db()) {
|
if !base_class.is_final(self.db()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -4736,10 +4749,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
|
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
|
||||||
match subclass_of.subclass_of() {
|
match subclass_of.subclass_of() {
|
||||||
ClassBase::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
!class.instance_member(db, attr).symbol.is_unbound()
|
!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"
|
"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),
|
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
|
||||||
(todo @ Type::Dynamic(DynamicType::Todo(_)), _, _)
|
(todo @ Type::Dynamic(DynamicType::Todo(_)), _, _)
|
||||||
| (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo),
|
| (_, todo @ Type::Dynamic(DynamicType::Todo(_)), _) => Some(todo),
|
||||||
(todo @ Type::Dynamic(DynamicType::TodoProtocol), _, _)
|
(todo @ Type::Dynamic(DynamicType::SubscriptedProtocol), _, _)
|
||||||
| (_, todo @ Type::Dynamic(DynamicType::TodoProtocol), _) => Some(todo),
|
| (_, 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::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
|
||||||
|
|
||||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some(
|
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => Some(
|
||||||
|
|
@ -6224,7 +6239,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::IntLiteral(i64::from(bool)),
|
Type::IntLiteral(i64::from(bool)),
|
||||||
),
|
),
|
||||||
(Type::KnownInstance(KnownInstanceType::Protocol), _) => {
|
(Type::KnownInstance(KnownInstanceType::Protocol), _) => {
|
||||||
Type::Dynamic(DynamicType::TodoProtocol)
|
Type::Dynamic(DynamicType::SubscriptedProtocol)
|
||||||
|
}
|
||||||
|
(Type::KnownInstance(KnownInstanceType::Generic), _) => {
|
||||||
|
Type::Dynamic(DynamicType::SubscriptedGeneric)
|
||||||
}
|
}
|
||||||
(Type::KnownInstance(known_instance), _)
|
(Type::KnownInstance(known_instance), _)
|
||||||
if known_instance.class().is_special_form() =>
|
if known_instance.class().is_special_form() =>
|
||||||
|
|
@ -6331,12 +6349,19 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(
|
report_non_subscriptable(
|
||||||
&self.context,
|
&self.context,
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
value_ty,
|
value_ty,
|
||||||
"__class_getitem__",
|
"__class_getitem__",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
report_non_subscriptable(
|
report_non_subscriptable(
|
||||||
&self.context,
|
&self.context,
|
||||||
|
|
@ -7508,7 +7533,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
KnownInstanceType::Protocol => {
|
KnownInstanceType::Protocol => {
|
||||||
self.infer_type_expression(arguments_slice);
|
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::NoReturn
|
||||||
| KnownInstanceType::Never
|
| KnownInstanceType::Never
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::symbol::SymbolAndQualifiers;
|
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`.
|
/// 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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
pub struct SubclassOfType<'db> {
|
pub struct SubclassOfType<'db> {
|
||||||
// Keep this field private, so that the only way of constructing the struct is through the `from` method.
|
// 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> {
|
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
|
/// 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.
|
/// 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();
|
let subclass_of = subclass_of.into();
|
||||||
match subclass_of {
|
match subclass_of {
|
||||||
ClassBase::Dynamic(_) => Type::SubclassOf(Self { subclass_of }),
|
SubclassOfInner::Dynamic(_) => Type::SubclassOf(Self { subclass_of }),
|
||||||
ClassBase::Class(class) => {
|
SubclassOfInner::Class(class) => {
|
||||||
if class.is_final(db) {
|
if class.is_final(db) {
|
||||||
Type::from(class)
|
Type::from(class)
|
||||||
} else if class.is_object(db) {
|
} else if class.is_object(db) {
|
||||||
|
|
@ -40,19 +40,19 @@ impl<'db> SubclassOfType<'db> {
|
||||||
/// Return a [`Type`] instance representing the type `type[Unknown]`.
|
/// Return a [`Type`] instance representing the type `type[Unknown]`.
|
||||||
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
|
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(SubclassOfType {
|
||||||
subclass_of: ClassBase::unknown(),
|
subclass_of: SubclassOfInner::unknown(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a [`Type`] instance representing the type `type[Any]`.
|
/// Return a [`Type`] instance representing the type `type[Any]`.
|
||||||
pub(crate) const fn subclass_of_any() -> Type<'db> {
|
pub(crate) const fn subclass_of_any() -> Type<'db> {
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(SubclassOfType {
|
||||||
subclass_of: ClassBase::any(),
|
subclass_of: SubclassOfInner::Dynamic(DynamicType::Any),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the inner [`ClassBase`] value wrapped by this `SubclassOfType`.
|
/// Return the inner [`SubclassOfInner`] value wrapped by this `SubclassOfType`.
|
||||||
pub(crate) const fn subclass_of(self) -> ClassBase<'db> {
|
pub(crate) const fn subclass_of(self) -> SubclassOfInner<'db> {
|
||||||
self.subclass_of
|
self.subclass_of
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,17 +77,17 @@ impl<'db> SubclassOfType<'db> {
|
||||||
|
|
||||||
/// Return `true` if `self` is a subtype of `other`.
|
/// 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.
|
/// only fully static types participate in subtyping.
|
||||||
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool {
|
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool {
|
||||||
match (self.subclass_of, other.subclass_of) {
|
match (self.subclass_of, other.subclass_of) {
|
||||||
// Non-fully-static types do not participate in subtyping
|
// 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`,
|
// 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`.
|
// 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`.
|
// 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
|
// N.B. The subclass relation is fully static
|
||||||
self_class.is_subclass_of(db, other_class)
|
self_class.is_subclass_of(db, other_class)
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +96,73 @@ impl<'db> SubclassOfType<'db> {
|
||||||
|
|
||||||
pub(crate) fn to_instance(self) -> Type<'db> {
|
pub(crate) fn to_instance(self) -> Type<'db> {
|
||||||
match self.subclass_of {
|
match self.subclass_of {
|
||||||
ClassBase::Class(class) => Type::instance(class),
|
SubclassOfInner::Class(class) => Type::instance(class),
|
||||||
ClassBase::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
|
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 crate::db::Db;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
class_base::ClassBase, DynamicType, InstanceType, KnownInstanceType, SuperOwnerKind, TodoType,
|
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, InstanceType,
|
||||||
Type,
|
KnownInstanceType, SuperOwnerKind, TodoType, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Return an [`Ordering`] that describes the canonical order in which two types should appear
|
/// 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)) => {
|
(Type::SubclassOf(left), Type::SubclassOf(right)) => {
|
||||||
match (left.subclass_of(), right.subclass_of()) {
|
match (left.subclass_of(), right.subclass_of()) {
|
||||||
(ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right),
|
(SubclassOfInner::Class(left), SubclassOfInner::Class(right)) => left.cmp(&right),
|
||||||
(ClassBase::Class(_), _) => Ordering::Less,
|
(SubclassOfInner::Class(_), _) => Ordering::Less,
|
||||||
(_, ClassBase::Class(_)) => Ordering::Greater,
|
(_, SubclassOfInner::Class(_)) => Ordering::Greater,
|
||||||
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
(SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => {
|
||||||
dynamic_elements_ordering(left, 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(left), ClassBase::Class(right)) => left.cmp(right),
|
||||||
(ClassBase::Class(_), _) => Ordering::Less,
|
(ClassBase::Class(_), _) => Ordering::Less,
|
||||||
(_, ClassBase::Class(_)) => Ordering::Greater,
|
(_, 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)) => {
|
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
||||||
dynamic_elements_ordering(*left, *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::Less,
|
||||||
(_, KnownInstanceType::OrderedDict) => Ordering::Greater,
|
(_, KnownInstanceType::OrderedDict) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Generic, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Generic) => Ordering::Greater,
|
||||||
|
|
||||||
(KnownInstanceType::Protocol, _) => Ordering::Less,
|
(KnownInstanceType::Protocol, _) => Ordering::Less,
|
||||||
(_, KnownInstanceType::Protocol) => Ordering::Greater,
|
(_, KnownInstanceType::Protocol) => Ordering::Greater,
|
||||||
|
|
||||||
|
|
@ -364,7 +371,10 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
|
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
|
||||||
|
|
||||||
(DynamicType::TodoProtocol, _) => Ordering::Less,
|
(DynamicType::SubscriptedGeneric, _) => Ordering::Less,
|
||||||
(_, DynamicType::TodoProtocol) => Ordering::Greater,
|
(_, DynamicType::SubscriptedGeneric) => Ordering::Greater,
|
||||||
|
|
||||||
|
(DynamicType::SubscriptedProtocol, _) => Ordering::Less,
|
||||||
|
(_, DynamicType::SubscriptedProtocol) => Ordering::Greater,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue