mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-29 03:02:27 +00:00
[ty] Fix bug where ty would think all types had an __mro__ attribute (#20995)
This commit is contained in:
parent
3c7f56f582
commit
db0e921db1
32 changed files with 780 additions and 599 deletions
|
|
@ -20,6 +20,6 @@ scope-existing-over-new-import,main.py,0,474
|
|||
scope-prioritize-closer,main.py,0,2
|
||||
scope-simple-long-identifier,main.py,0,1
|
||||
tstring-completions,main.py,0,1
|
||||
ty-extensions-lower-stdlib,main.py,0,7
|
||||
ty-extensions-lower-stdlib,main.py,0,8
|
||||
type-var-typing-over-ast,main.py,0,3
|
||||
type-var-typing-over-ast,main.py,1,270
|
||||
|
|
|
|||
|
|
|
@ -1864,7 +1864,7 @@ C.<CURSOR>
|
|||
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'C'>, <class 'object'>]
|
||||
__mro__ :: tuple[type, ...]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self@__new__
|
||||
|
|
@ -1933,7 +1933,7 @@ Meta.<CURSOR>
|
|||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'Meta'>, <class 'type'>, <class 'object'>]
|
||||
__mro__ :: tuple[type, ...]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__or__ :: def __or__[Self](self: Self@__or__, value: Any, /) -> UnionType | Self@__or__
|
||||
|
|
@ -2061,7 +2061,7 @@ Quux.<CURSOR>
|
|||
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'Quux'>, <class 'object'>]
|
||||
__mro__ :: tuple[type, ...]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self@__new__
|
||||
|
|
@ -2138,7 +2138,7 @@ Answer.<CURSOR>
|
|||
__len__ :: bound method <class 'Answer'>.__len__() -> int
|
||||
__members__ :: MappingProxyType[str, Unknown]
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>]
|
||||
__mro__ :: tuple[type, ...]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls, value: object) -> Self@__new__
|
||||
|
|
|
|||
|
|
@ -72,21 +72,23 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
|
|||
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class C(Annotated[int, "foo"]): ...
|
||||
|
||||
# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]`
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>]
|
||||
# TODO: Should be `(<class 'C'>, <class 'int'>, <class 'object'>)`
|
||||
reveal_mro(C) # revealed: (<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>)
|
||||
```
|
||||
|
||||
### Not parameterized
|
||||
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# At runtime, this is an error.
|
||||
# error: [invalid-base]
|
||||
class C(Annotated): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -56,10 +56,11 @@ allowed, even when the unknown superclass is `int`. The assignment to `y` should
|
|||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class SubclassOfAny(Any): ...
|
||||
|
||||
reveal_type(SubclassOfAny.__mro__) # revealed: tuple[<class 'SubclassOfAny'>, Any, <class 'object'>]
|
||||
reveal_mro(SubclassOfAny) # revealed: (<class 'SubclassOfAny'>, Any, <class 'object'>)
|
||||
|
||||
x: SubclassOfAny = 1 # error: [invalid-assignment]
|
||||
y: int = SubclassOfAny()
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ The aliases can be inherited from. Some of these are still partially or wholly T
|
|||
|
||||
```py
|
||||
import typing
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
####################
|
||||
### Built-ins
|
||||
|
|
@ -121,23 +122,23 @@ import typing
|
|||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
# revealed: (<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(ListSubclass)
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
# revealed: (<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DictSubclass)
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
# revealed: (<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(SetSubclass)
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(FrozenSetSubclass.__mro__)
|
||||
# revealed: (<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(FrozenSetSubclass)
|
||||
|
||||
####################
|
||||
### `collections`
|
||||
|
|
@ -145,26 +146,26 @@ reveal_type(FrozenSetSubclass.__mro__)
|
|||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
# revealed: (<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(ChainMapSubclass)
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
# revealed: (<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(CounterSubclass)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
# revealed: (<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DefaultDictSubclass)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
# revealed: (<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DequeSubclass)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
# revealed: (<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(OrderedDictSubclass)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -78,6 +78,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, Generic
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A(Self): ... # error: [invalid-base]
|
||||
class B(Unpack): ... # error: [invalid-base]
|
||||
|
|
@ -87,7 +88,7 @@ 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[<class 'F'>, @Todo(Support for Callable as a base class), <class 'object'>]
|
||||
reveal_mro(F) # revealed: (<class 'F'>, @Todo(Support for Callable as a base class), <class 'object'>)
|
||||
```
|
||||
|
||||
## Subscriptability
|
||||
|
|
|
|||
|
|
@ -1468,6 +1468,8 @@ C.X = "bar"
|
|||
### Multiple inheritance
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class O: ...
|
||||
|
||||
class F(O):
|
||||
|
|
@ -1481,8 +1483,8 @@ class C(D, F): ...
|
|||
class B(E, D): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
# revealed: (<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(A)
|
||||
|
||||
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
||||
reveal_type(A.X) # revealed: Unknown | Literal[42]
|
||||
|
|
@ -1682,6 +1684,7 @@ Similar principles apply if `Any` appears in the middle of an inheritance hierar
|
|||
|
||||
```py
|
||||
from typing import ClassVar, Literal
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A:
|
||||
x: ClassVar[Literal[1]] = 1
|
||||
|
|
@ -1689,7 +1692,7 @@ class A:
|
|||
class B(Any): ...
|
||||
class C(B, A): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, Any, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, Any, <class 'A'>, <class 'object'>)
|
||||
reveal_type(C.x) # revealed: Literal[1] & Any
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ python-version = "3.12"
|
|||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A:
|
||||
def a(self): ...
|
||||
|
|
@ -39,7 +40,7 @@ class C(B):
|
|||
def c(self): ...
|
||||
cc: int = 3
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
|
||||
super(C, C()).a
|
||||
super(C, C()).b
|
||||
|
|
@ -420,6 +421,8 @@ When the owner is a union type, `super()` is built separately for each branch, a
|
|||
super objects are combined into a union.
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A: ...
|
||||
|
||||
class B:
|
||||
|
|
@ -429,8 +432,8 @@ class C(A, B): ...
|
|||
class D(B, A): ...
|
||||
|
||||
def f(x: C | D):
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
|
||||
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
|
||||
reveal_mro(D) # revealed: (<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
|
||||
s = super(A, x)
|
||||
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ python-version = "3.12"
|
|||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
A = int
|
||||
|
||||
class G[T]: ...
|
||||
|
|
@ -21,5 +23,5 @@ class C(A, G["B"]): ...
|
|||
A = str
|
||||
B = bytes
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1173,6 +1173,7 @@ and attributes like the MRO are unchanged:
|
|||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
@dataclass
|
||||
class Person:
|
||||
|
|
@ -1180,7 +1181,8 @@ class Person:
|
|||
age: int | None = None
|
||||
|
||||
reveal_type(type(Person)) # revealed: <class 'type'>
|
||||
reveal_type(Person.__mro__) # revealed: tuple[<class 'Person'>, <class 'object'>]
|
||||
reveal_type(Person.__mro__) # revealed: tuple[type, ...]
|
||||
reveal_mro(Person) # revealed: (<class 'Person'>, <class 'object'>)
|
||||
```
|
||||
|
||||
The generated methods have the following signatures:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ At its simplest, to define a generic class using PEP 695 syntax, you add a list
|
|||
`ParamSpec`s or `TypeVarTuple`s after the class name.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context
|
||||
from ty_extensions import generic_context, reveal_mro
|
||||
|
||||
class SingleTypevar[T]: ...
|
||||
class MultipleTypevars[T, S]: ...
|
||||
|
|
@ -77,13 +77,13 @@ T = TypeVar("T")
|
|||
# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
|
||||
class BothGenericSyntaxes[U](Generic[T]): ...
|
||||
|
||||
reveal_type(BothGenericSyntaxes.__mro__) # revealed: tuple[<class 'BothGenericSyntaxes[Unknown]'>, Unknown, <class 'object'>]
|
||||
reveal_mro(BothGenericSyntaxes) # revealed: (<class 'BothGenericSyntaxes[Unknown]'>, Unknown, <class 'object'>)
|
||||
|
||||
# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
|
||||
# error: [invalid-base] "Cannot inherit from plain `Generic`"
|
||||
class DoublyInvalid[T](Generic): ...
|
||||
|
||||
reveal_type(DoublyInvalid.__mro__) # revealed: tuple[<class 'DoublyInvalid[Unknown]'>, Unknown, <class 'object'>]
|
||||
reveal_mro(DoublyInvalid) # revealed: (<class 'DoublyInvalid[Unknown]'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
Generic classes implicitly inherit from `Generic`:
|
||||
|
|
@ -91,26 +91,26 @@ Generic classes implicitly inherit from `Generic`:
|
|||
```py
|
||||
class Foo[T]: ...
|
||||
|
||||
# revealed: tuple[<class 'Foo[Unknown]'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Foo.__mro__)
|
||||
# revealed: tuple[<class 'Foo[int]'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Foo[int].__mro__)
|
||||
# revealed: (<class 'Foo[Unknown]'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Foo)
|
||||
# revealed: (<class 'Foo[int]'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Foo[int])
|
||||
|
||||
class A: ...
|
||||
class Bar[T](A): ...
|
||||
|
||||
# revealed: tuple[<class 'Bar[Unknown]'>, <class 'A'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Bar.__mro__)
|
||||
# revealed: tuple[<class 'Bar[int]'>, <class 'A'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Bar[int].__mro__)
|
||||
# revealed: (<class 'Bar[Unknown]'>, <class 'A'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Bar)
|
||||
# revealed: (<class 'Bar[int]'>, <class 'A'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Bar[int])
|
||||
|
||||
class B: ...
|
||||
class Baz[T](A, B): ...
|
||||
|
||||
# revealed: tuple[<class 'Baz[Unknown]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Baz.__mro__)
|
||||
# revealed: tuple[<class 'Baz[int]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Baz[int].__mro__)
|
||||
# revealed: (<class 'Baz[Unknown]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Baz)
|
||||
# revealed: (<class 'Baz[int]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Baz[int])
|
||||
```
|
||||
|
||||
## Specializing generic classes explicitly
|
||||
|
|
|
|||
|
|
@ -67,22 +67,25 @@ x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]"
|
|||
`a.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A: ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'object'>]
|
||||
reveal_mro(A) # revealed: (<class 'A'>, <class 'object'>)
|
||||
import b
|
||||
|
||||
class C(b.B): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
```
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
from a import A
|
||||
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(B) # revealed: (<class 'B'>, <class 'A'>, <class 'object'>)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -798,11 +798,11 @@ A class literal can be iterated over if it has `Any` or `Unknown` in its MRO, si
|
|||
```py
|
||||
from unresolved_module import SomethingUnknown # error: [unresolved-import]
|
||||
from typing import Any, Iterable
|
||||
from ty_extensions import static_assert, is_assignable_to, TypeOf, Unknown
|
||||
from ty_extensions import static_assert, is_assignable_to, TypeOf, Unknown, reveal_mro
|
||||
|
||||
class Foo(SomethingUnknown): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(TypeOf[Foo], Iterable[Unknown])) # error: [static-assert-error]
|
||||
|
|
@ -815,7 +815,7 @@ for x in Foo:
|
|||
|
||||
class Bar(Any): ...
|
||||
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Any, <class 'object'>]
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, Any, <class 'object'>)
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(TypeOf[Bar], Iterable[Any])) # error: [static-assert-error]
|
||||
|
|
|
|||
|
|
@ -1,55 +1,74 @@
|
|||
# Method Resolution Order tests
|
||||
|
||||
Tests that assert that we can infer the correct type for a class's `__mro__` attribute.
|
||||
Tests that assert that we can infer the correct MRO for a class.
|
||||
|
||||
This attribute is rarely accessed directly at runtime. However, it's extremely important for *us* to
|
||||
know the precise possible values of a class's Method Resolution Order, or we won't be able to infer
|
||||
the correct type of attributes accessed from instances.
|
||||
It's extremely important for us to know the precise possible values of a class's Method Resolution
|
||||
Order, or we won't be able to infer the correct type of attributes accessed from instances.
|
||||
|
||||
For documentation on method resolution orders, see:
|
||||
|
||||
- <https://docs.python.org/3/glossary.html#term-method-resolution-order>
|
||||
- <https://docs.python.org/3/howto/mro.html#python-2-3-mro>
|
||||
|
||||
At runtime, the MRO for a class can be inspected using the `__mro__` attribute. However, rather than
|
||||
special-casing inference of that attribute, we allow our inferred MRO of a class to be introspected
|
||||
using the `ty_extensions.reveal_mro` function. This is because the MRO ty infers for a class will
|
||||
often be different than a class's "real MRO" at runtime. This is often deliberate and desirable, but
|
||||
would be confusing to users. For example, typeshed pretends that builtin sequences such as `tuple`
|
||||
and `list` inherit from `collections.abc.Sequence`, resulting in a much longer inferred MRO for
|
||||
these classes than what they actually have at runtime. Other differences to "real MROs" at runtime
|
||||
include the facts that ty's inferred MRO will often include non-class elements, such as generic
|
||||
aliases, `Any` and `Unknown`.
|
||||
|
||||
## No bases
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class C: ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## The special case: `object` itself
|
||||
|
||||
```py
|
||||
reveal_type(object.__mro__) # revealed: tuple[<class 'object'>]
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
reveal_mro(object) # revealed: (<class 'object'>,)
|
||||
```
|
||||
|
||||
## Explicit inheritance from `object`
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class C(object): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## Explicit inheritance from non-`object` single base
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
|
||||
reveal_mro(B) # revealed: (<class 'B'>, <class 'A'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## Linearization of multiple bases
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (1)
|
||||
|
|
@ -57,14 +76,16 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>,
|
|||
This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
class A(X, Y): ...
|
||||
class B(Y, X): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
|
||||
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(B) # revealed: (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (2)
|
||||
|
|
@ -72,6 +93,8 @@ reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>,
|
|||
This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class O: ...
|
||||
class F(O): ...
|
||||
class E(O): ...
|
||||
|
|
@ -80,12 +103,12 @@ class C(D, F): ...
|
|||
class B(D, E): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: tuple[<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__)
|
||||
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
# revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
# revealed: (<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(B)
|
||||
# revealed: (<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(A)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (3)
|
||||
|
|
@ -93,6 +116,8 @@ reveal_type(A.__mro__)
|
|||
This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class O: ...
|
||||
class F(O): ...
|
||||
class E(O): ...
|
||||
|
|
@ -101,12 +126,12 @@ class C(D, F): ...
|
|||
class B(E, D): ...
|
||||
class A(B, C): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: tuple[<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__)
|
||||
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
# revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
# revealed: (<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(B)
|
||||
# revealed: (<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(A)
|
||||
```
|
||||
|
||||
## Complex diamond inheritance (4)
|
||||
|
|
@ -114,6 +139,8 @@ reveal_type(A.__mro__)
|
|||
This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class O: ...
|
||||
class A(O): ...
|
||||
class B(O): ...
|
||||
|
|
@ -125,19 +152,20 @@ class K2(D, B, E): ...
|
|||
class K3(D, A): ...
|
||||
class Z(K1, K2, K3): ...
|
||||
|
||||
# revealed: tuple[<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(K1.__mro__)
|
||||
# revealed: tuple[<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(K2.__mro__)
|
||||
# revealed: tuple[<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(K3.__mro__)
|
||||
# revealed: tuple[<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(Z.__mro__)
|
||||
# revealed: (<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(K1)
|
||||
# revealed: (<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(K2)
|
||||
# revealed: (<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(K3)
|
||||
# revealed: (<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(Z)
|
||||
```
|
||||
|
||||
## Inheritance from `Unknown`
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
from does_not_exist import DoesNotExist # error: [unresolved-import]
|
||||
|
||||
class A(DoesNotExist): ...
|
||||
|
|
@ -147,11 +175,11 @@ class D(A, B, C): ...
|
|||
class E(B, C): ...
|
||||
class F(E, A): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, Unknown, <class 'object'>]
|
||||
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>]
|
||||
reveal_type(E.__mro__) # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>]
|
||||
# revealed: tuple[<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>]
|
||||
reveal_type(F.__mro__)
|
||||
reveal_mro(A) # revealed: (<class 'A'>, Unknown, <class 'object'>)
|
||||
reveal_mro(D) # revealed: (<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>)
|
||||
reveal_mro(E) # revealed: (<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>)
|
||||
# revealed: (<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>)
|
||||
reveal_mro(F)
|
||||
```
|
||||
|
||||
## Inheritance with intersections that include `Unknown`
|
||||
|
|
@ -160,23 +188,22 @@ An intersection that includes `Unknown` or `Any` is permitted as long as the int
|
|||
disjoint from `type`.
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
from does_not_exist import DoesNotExist # error: [unresolved-import]
|
||||
|
||||
reveal_type(DoesNotExist) # revealed: Unknown
|
||||
|
||||
if hasattr(DoesNotExist, "__mro__"):
|
||||
# TODO: this should be `Unknown & <Protocol with members '__mro__'>` or similar
|
||||
# (The second part of the intersection is incorrectly simplified to `object` due to https://github.com/astral-sh/ty/issues/986)
|
||||
reveal_type(DoesNotExist) # revealed: Unknown
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & <Protocol with members '__mro__'>
|
||||
|
||||
class Foo(DoesNotExist): ... # no error!
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
if not isinstance(DoesNotExist, type):
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & ~type
|
||||
|
||||
class Foo(DoesNotExist): ... # error: [unsupported-base]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Inheritance from `type[Any]` and `type[Unknown]`
|
||||
|
|
@ -186,14 +213,14 @@ guarantee:
|
|||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import Unknown, Intersection
|
||||
from ty_extensions import Unknown, Intersection, reveal_mro
|
||||
|
||||
def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
|
||||
class Foo(x): ...
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Any, <class 'object'>)
|
||||
|
||||
class Bar(y): ...
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors at runtime
|
||||
|
|
@ -202,14 +229,16 @@ If the class's `__bases__` cause an exception to be raised at runtime and theref
|
|||
creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
|
||||
class Foo(object, int): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
class Bar(Foo): ...
|
||||
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
# This is the `TypeError` at the bottom of "ex_2"
|
||||
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
|
||||
|
|
@ -219,17 +248,17 @@ class Y(O): ...
|
|||
class A(X, Y): ...
|
||||
class B(Y, X): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
|
||||
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(B) # revealed: (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
|
||||
|
||||
class AA(Z): ...
|
||||
|
||||
reveal_type(AA.__mro__) # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>]
|
||||
reveal_mro(AA) # revealed: (<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` includes a `Union`
|
||||
|
|
@ -241,6 +270,8 @@ find a union type in a class's bases, we infer the class's `__mro__` as being
|
|||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
|
|
@ -257,7 +288,7 @@ reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
|||
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
class Foo(x): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` is a union of a dynamic type and valid bases
|
||||
|
|
@ -268,6 +299,7 @@ diagnostic, and we use the dynamic type as a base to prevent further downstream
|
|||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
def _(flag: bool, any: Any):
|
||||
if flag:
|
||||
|
|
@ -276,12 +308,14 @@ def _(flag: bool, any: Any):
|
|||
class Base: ...
|
||||
|
||||
class Foo(Base): ...
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Any, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` includes multiple `Union`s
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
|
|
@ -307,12 +341,14 @@ reveal_type(y) # revealed: <class 'C'> | <class 'D'>
|
|||
# error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
|
||||
class Foo(x, y): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors... now with `Union`s
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
|
|
@ -328,11 +364,11 @@ else:
|
|||
# error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
|
||||
class PossibleError(foo, X): ...
|
||||
|
||||
reveal_type(PossibleError.__mro__) # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
|
||||
reveal_mro(PossibleError) # revealed: (<class 'PossibleError'>, Unknown, <class 'object'>)
|
||||
|
||||
class A(X, Y): ...
|
||||
|
||||
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
|
||||
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
|
||||
|
||||
if returns_bool():
|
||||
class B(X, Y): ...
|
||||
|
|
@ -340,13 +376,13 @@ if returns_bool():
|
|||
else:
|
||||
class B(Y, X): ...
|
||||
|
||||
# revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__)
|
||||
# revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(B)
|
||||
|
||||
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## `__bases__` lists that include objects that are not instances of `type`
|
||||
|
|
@ -389,9 +425,11 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
|
|||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
class Spam: ...
|
||||
class Eggs: ...
|
||||
|
|
@ -413,12 +451,12 @@ class Ham(
|
|||
|
||||
# fmt: on
|
||||
|
||||
reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Ham) # revealed: (<class 'Ham'>, Unknown, <class 'object'>)
|
||||
|
||||
class Mushrooms: ...
|
||||
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
|
||||
reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
|
||||
|
||||
# fmt: off
|
||||
|
||||
|
|
@ -494,6 +532,7 @@ however, for gradual types this would break the
|
|||
the dynamic base can usually be materialised to a type that would lead to a resolvable MRO.
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
from unresolvable_module import UnknownBase1, UnknownBase2 # error: [unresolved-import]
|
||||
|
||||
reveal_type(UnknownBase1) # revealed: Unknown
|
||||
|
|
@ -502,7 +541,7 @@ reveal_type(UnknownBase2) # revealed: Unknown
|
|||
# no error here -- we respect the gradual guarantee:
|
||||
class Foo(UnknownBase1, UnknownBase2): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
However, if there are duplicate class elements, we do emit an error, even if there are also multiple
|
||||
|
|
@ -513,7 +552,7 @@ bases materialize to:
|
|||
# error: [duplicate-base] "Duplicate base class `Foo`"
|
||||
class Bar(UnknownBase1, Foo, UnknownBase2, Foo): ...
|
||||
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
|
||||
|
|
@ -529,25 +568,26 @@ reveal_type(unknown_object.__mro__) # revealed: Unknown
|
|||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Iterator
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class peekable(Generic[T], Iterator[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable.__mro__)
|
||||
# revealed: (<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(peekable)
|
||||
|
||||
class peekable2(Iterator[T], Generic[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable2.__mro__)
|
||||
# revealed: (<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(peekable2)
|
||||
|
||||
class Base: ...
|
||||
class Intermediate(Base, Generic[T]): ...
|
||||
class Sub(Intermediate[T], Base): ...
|
||||
|
||||
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Sub.__mro__)
|
||||
# revealed: (<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Sub)
|
||||
```
|
||||
|
||||
## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
|
||||
|
|
@ -569,17 +609,19 @@ class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
|||
These are invalid, but we need to be able to handle them gracefully without panicking.
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Foo(Foo): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo) # revealed: <class 'Foo'>
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
||||
class Bar: ...
|
||||
class Baz: ...
|
||||
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Boz) # revealed: <class 'Boz'>
|
||||
reveal_type(Boz.__mro__) # revealed: tuple[<class 'Boz'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Boz) # revealed: (<class 'Boz'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Classes with indirect cycles in their MROs
|
||||
|
|
@ -587,31 +629,37 @@ reveal_type(Boz.__mro__) # revealed: tuple[<class 'Boz'>, Unknown, <class 'obje
|
|||
These are similarly unlikely, but we still shouldn't crash:
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
||||
class Baz(Foo): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Classes with cycles in their MROs, and multiple inheritance
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Spam: ...
|
||||
class Foo(Bar): ... # error: [cyclic-class-definition]
|
||||
class Bar(Baz): ... # error: [cyclic-class-definition]
|
||||
class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Classes with cycles in their MRO, and a sub-graph
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class FooCycle(BarCycle): ... # error: [cyclic-class-definition]
|
||||
class Foo: ...
|
||||
class BarCycle(FooCycle): ... # error: [cyclic-class-definition]
|
||||
|
|
@ -622,10 +670,10 @@ class Bar(Foo): ...
|
|||
class Baz(Bar, BarCycle): ...
|
||||
class Spam(Baz): ...
|
||||
|
||||
reveal_type(FooCycle.__mro__) # revealed: tuple[<class 'FooCycle'>, Unknown, <class 'object'>]
|
||||
reveal_type(BarCycle.__mro__) # revealed: tuple[<class 'BarCycle'>, Unknown, <class 'object'>]
|
||||
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
|
||||
reveal_type(Spam.__mro__) # revealed: tuple[<class 'Spam'>, Unknown, <class 'object'>]
|
||||
reveal_mro(FooCycle) # revealed: (<class 'FooCycle'>, Unknown, <class 'object'>)
|
||||
reveal_mro(BarCycle) # revealed: (<class 'BarCycle'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
|
||||
reveal_mro(Spam) # revealed: (<class 'Spam'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
## Other classes with possible cycles
|
||||
|
|
@ -636,20 +684,22 @@ python-version = "3.13"
|
|||
```
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class C(C.a): ...
|
||||
reveal_type(C.__class__) # revealed: <class 'type'>
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
|
||||
|
||||
class D(D.a):
|
||||
a: D
|
||||
reveal_type(D.__class__) # revealed: <class 'type'>
|
||||
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, Unknown, <class 'object'>]
|
||||
reveal_mro(D) # revealed: (<class 'D'>, Unknown, <class 'object'>)
|
||||
|
||||
class E[T](E.a): ...
|
||||
reveal_type(E.__class__) # revealed: <class 'type'>
|
||||
reveal_type(E.__mro__) # revealed: tuple[<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>]
|
||||
reveal_mro(E) # revealed: (<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>)
|
||||
|
||||
class F[T](F(), F): ... # error: [cyclic-class-definition]
|
||||
reveal_type(F.__class__) # revealed: type[Unknown]
|
||||
reveal_type(F.__mro__) # revealed: tuple[<class 'F[Unknown]'>, Unknown, <class 'object'>]
|
||||
reveal_mro(F) # revealed: (<class 'F[Unknown]'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ name, and not just by its numeric position within the tuple:
|
|||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import static_assert, is_subtype_of, is_assignable_to
|
||||
from ty_extensions import static_assert, is_subtype_of, is_assignable_to, reveal_mro
|
||||
|
||||
class Person(NamedTuple):
|
||||
id: int
|
||||
|
|
@ -25,8 +25,8 @@ reveal_type(alice.id) # revealed: int
|
|||
reveal_type(alice.name) # revealed: str
|
||||
reveal_type(alice.age) # revealed: int | None
|
||||
|
||||
# revealed: tuple[<class 'Person'>, <class 'tuple[int, str, int | None]'>, <class 'Sequence[int | str | None]'>, <class 'Reversible[int | str | None]'>, <class 'Collection[int | str | None]'>, <class 'Iterable[int | str | None]'>, <class 'Container[int | str | None]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(Person.__mro__)
|
||||
# revealed: (<class 'Person'>, <class 'tuple[int, str, int | None]'>, <class 'Sequence[int | str | None]'>, <class 'Reversible[int | str | None]'>, <class 'Collection[int | str | None]'>, <class 'Iterable[int | str | None]'>, <class 'Container[int | str | None]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Person)
|
||||
|
||||
static_assert(is_subtype_of(Person, tuple[int, str, int | None]))
|
||||
static_assert(is_subtype_of(Person, tuple[object, ...]))
|
||||
|
|
@ -329,9 +329,8 @@ reveal_type(typing.NamedTuple.__name__) # revealed: str
|
|||
reveal_type(typing.NamedTuple.__qualname__) # revealed: str
|
||||
reveal_type(typing.NamedTuple.__kwdefaults__) # revealed: dict[str, Any] | None
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic and reveal `Unknown` (function objects don't have an `__mro__` attribute),
|
||||
# but the fact that we don't isn't actually a `NamedTuple` bug (https://github.com/astral-sh/ty/issues/986)
|
||||
reveal_type(typing.NamedTuple.__mro__) # revealed: tuple[<class 'FunctionType'>, <class 'object'>]
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(typing.NamedTuple.__mro__) # revealed: Unknown
|
||||
```
|
||||
|
||||
By the normal rules, `NamedTuple` and `type[NamedTuple]` should not be valid in type expressions --
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ A protocol is defined by inheriting from the `Protocol` class, which is annotate
|
|||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class MyProtocol(Protocol): ...
|
||||
|
||||
reveal_type(MyProtocol.__mro__) # revealed: tuple[<class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_mro(MyProtocol) # revealed: (<class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
```
|
||||
|
||||
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
|
||||
|
|
@ -37,7 +38,7 @@ class's bases:
|
|||
```py
|
||||
class Foo(Protocol, Protocol): ... # error: [duplicate-base]
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
Protocols can also be generic, either by including `Generic[]` in the bases list, subscripting
|
||||
|
|
@ -64,7 +65,7 @@ class Bar3[T](Protocol[T]):
|
|||
|
||||
# Note that this class definition *will* actually succeed at runtime,
|
||||
# unlike classes that combine PEP-695 type parameters with inheritance from `Generic[]`
|
||||
reveal_type(Bar3.__mro__) # revealed: tuple[<class 'Bar3[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_mro(Bar3) # revealed: (<class 'Bar3[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
```
|
||||
|
||||
It's an error to include both bare `Protocol` and subscripted `Protocol[]` in the bases list
|
||||
|
|
@ -74,8 +75,8 @@ simultaneously:
|
|||
class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
|
||||
x: T
|
||||
|
||||
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>]
|
||||
reveal_type(DuplicateBases.__mro__)
|
||||
# revealed: (<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>)
|
||||
reveal_mro(DuplicateBases)
|
||||
```
|
||||
|
||||
The introspection helper `typing(_extensions).is_protocol` can be used to verify whether a class is
|
||||
|
|
@ -124,8 +125,8 @@ it is not sufficient for it to have `Protocol` in its MRO.
|
|||
```py
|
||||
class SubclassOfMyProtocol(MyProtocol): ...
|
||||
|
||||
# revealed: tuple[<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(SubclassOfMyProtocol.__mro__)
|
||||
# revealed: (<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(SubclassOfMyProtocol)
|
||||
|
||||
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False]
|
||||
```
|
||||
|
|
@ -143,8 +144,8 @@ class OtherProtocol(Protocol):
|
|||
|
||||
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
|
||||
|
||||
# revealed: tuple[<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(ComplexInheritance.__mro__)
|
||||
# revealed: (<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(ComplexInheritance)
|
||||
|
||||
reveal_type(is_protocol(ComplexInheritance)) # revealed: Literal[True]
|
||||
```
|
||||
|
|
@ -156,22 +157,22 @@ or `TypeError` is raised at runtime when the class is created.
|
|||
# error: [invalid-protocol] "Protocol class `Invalid` cannot inherit from non-protocol class `NotAProtocol`"
|
||||
class Invalid(NotAProtocol, Protocol): ...
|
||||
|
||||
# revealed: tuple[<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(Invalid.__mro__)
|
||||
# revealed: (<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(Invalid)
|
||||
|
||||
# error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`"
|
||||
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
|
||||
|
||||
# revealed: tuple[<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(AlsoInvalid.__mro__)
|
||||
# revealed: (<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(AlsoInvalid)
|
||||
|
||||
class NotAGenericProtocol[T]: ...
|
||||
|
||||
# error: [invalid-protocol] "Protocol class `StillInvalid` cannot inherit from non-protocol class `NotAGenericProtocol`"
|
||||
class StillInvalid(NotAGenericProtocol[int], Protocol): ...
|
||||
|
||||
# revealed: tuple[<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(StillInvalid.__mro__)
|
||||
# revealed: (<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(StillInvalid)
|
||||
```
|
||||
|
||||
But two exceptions to this rule are `object` and `Generic`:
|
||||
|
|
@ -188,7 +189,7 @@ T = TypeVar("T")
|
|||
# type checkers.
|
||||
class Fine(Protocol, object): ...
|
||||
|
||||
reveal_type(Fine.__mro__) # revealed: tuple[<class 'Fine'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_mro(Fine) # revealed: (<class 'Fine'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
|
||||
class StillFine(Protocol, Generic[T], object): ...
|
||||
class EvenThis[T](Protocol, object): ...
|
||||
|
|
@ -202,8 +203,8 @@ And multiple inheritance from a mix of protocol and non-protocol classes is fine
|
|||
```py
|
||||
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
|
||||
|
||||
# revealed: tuple[<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>]
|
||||
reveal_type(FineAndDandy.__mro__)
|
||||
# revealed: (<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>)
|
||||
reveal_mro(FineAndDandy)
|
||||
```
|
||||
|
||||
But if `Protocol` is not present in the bases list, the resulting class doesn't count as a protocol
|
||||
|
|
@ -270,11 +271,11 @@ second argument to `issubclass()` at runtime:
|
|||
```py
|
||||
import abc
|
||||
import typing
|
||||
from ty_extensions import TypeOf
|
||||
from ty_extensions import TypeOf, reveal_mro
|
||||
|
||||
reveal_type(type(Protocol)) # revealed: <class '_ProtocolMeta'>
|
||||
# revealed: tuple[<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>]
|
||||
reveal_type(type(Protocol).__mro__)
|
||||
# revealed: (<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>)
|
||||
reveal_mro(type(Protocol))
|
||||
static_assert(is_subtype_of(TypeOf[Protocol], type))
|
||||
static_assert(is_subtype_of(TypeOf[Protocol], abc.ABCMeta))
|
||||
static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta))
|
||||
|
|
|
|||
|
|
@ -12,36 +12,38 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
|||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def returns_bool() -> bool:
|
||||
2 | return True
|
||||
3 |
|
||||
4 | class A: ...
|
||||
5 | class B: ...
|
||||
6 |
|
||||
7 | if returns_bool():
|
||||
8 | x = A
|
||||
9 | else:
|
||||
10 | x = B
|
||||
11 |
|
||||
12 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
1 | from ty_extensions import reveal_mro
|
||||
2 |
|
||||
3 | def returns_bool() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | class A: ...
|
||||
7 | class B: ...
|
||||
8 |
|
||||
9 | if returns_bool():
|
||||
10 | x = A
|
||||
11 | else:
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
15 | class Foo(x): ...
|
||||
16 |
|
||||
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
|
||||
--> src/mdtest_snippet.py:15:11
|
||||
--> src/mdtest_snippet.py:17:11
|
||||
|
|
||||
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
15 | class Foo(x): ...
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
| ^
|
||||
16 |
|
||||
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
18 |
|
||||
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
|
|
|
|||
|
|
@ -12,109 +12,115 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
|||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
1 | from ty_extensions import reveal_mro
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
4 |
|
||||
5 | class Spam: ...
|
||||
6 | class Eggs: ...
|
||||
7 | class Bar: ...
|
||||
8 | class Baz: ...
|
||||
9 |
|
||||
10 | # fmt: off
|
||||
5 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
6 |
|
||||
7 | class Spam: ...
|
||||
8 | class Eggs: ...
|
||||
9 | class Bar: ...
|
||||
10 | class Baz: ...
|
||||
11 |
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
16 | Eggs,
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
20 | Eggs,
|
||||
21 | ): ...
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
12 | # fmt: off
|
||||
13 |
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
18 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
22 | Eggs,
|
||||
23 | ): ...
|
||||
24 |
|
||||
25 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
|
||||
25 | # fmt: on
|
||||
26 |
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
27 | reveal_mro(Ham) # revealed: (<class 'Ham'>, Unknown, <class 'object'>)
|
||||
28 |
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
31 |
|
||||
32 | # fmt: off
|
||||
32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
|
||||
33 |
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | Eggs,
|
||||
37 | Ham,
|
||||
38 | Spam,
|
||||
39 | Eggs,
|
||||
40 | Mushrooms,
|
||||
41 | Bar,
|
||||
42 | Eggs,
|
||||
43 | Baz,
|
||||
34 | # fmt: off
|
||||
35 |
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
38 | Eggs,
|
||||
39 | Ham,
|
||||
40 | Spam,
|
||||
41 | Eggs,
|
||||
42 | Mushrooms,
|
||||
43 | Bar,
|
||||
44 | Eggs,
|
||||
45 | ): ...
|
||||
46 |
|
||||
47 | # fmt: off
|
||||
48 | # fmt: off
|
||||
49 |
|
||||
50 | class A: ...
|
||||
45 | Baz,
|
||||
46 | Eggs,
|
||||
47 | ): ...
|
||||
48 |
|
||||
49 | # fmt: off
|
||||
50 | # fmt: off
|
||||
51 |
|
||||
52 | class B( # type: ignore[duplicate-base]
|
||||
53 | A,
|
||||
54 | A,
|
||||
55 | ): ...
|
||||
56 |
|
||||
57 | class C(
|
||||
58 | A,
|
||||
59 | A
|
||||
60 | ): # type: ignore[duplicate-base]
|
||||
61 | x: int
|
||||
62 |
|
||||
63 | # fmt: on
|
||||
64 | # fmt: off
|
||||
65 |
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | A,
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
71 | ): ...
|
||||
72 |
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | A,
|
||||
76 | A
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
80 |
|
||||
81 | # fmt: on
|
||||
52 | class A: ...
|
||||
53 |
|
||||
54 | class B( # type: ignore[duplicate-base]
|
||||
55 | A,
|
||||
56 | A,
|
||||
57 | ): ...
|
||||
58 |
|
||||
59 | class C(
|
||||
60 | A,
|
||||
61 | A
|
||||
62 | ): # type: ignore[duplicate-base]
|
||||
63 | x: int
|
||||
64 |
|
||||
65 | # fmt: on
|
||||
66 | # fmt: off
|
||||
67 |
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
70 | A,
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
73 | ): ...
|
||||
74 |
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
77 | A,
|
||||
78 | A
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
82 |
|
||||
83 | # fmt: on
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `str`
|
||||
--> src/mdtest_snippet.py:1:7
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| ^^^^^^^^^^^^^
|
||||
1 | from ty_extensions import reveal_mro
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| ^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: The definition of class `Foo` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:1:11
|
||||
--> src/mdtest_snippet.py:3:11
|
||||
|
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
1 | from ty_extensions import reveal_mro
|
||||
2 |
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| --- ^^^ Class `str` later repeated here
|
||||
| |
|
||||
| Class `str` first included in bases list here
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
4 |
|
||||
5 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -122,37 +128,37 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Spam`
|
||||
--> src/mdtest_snippet.py:14:7
|
||||
--> src/mdtest_snippet.py:16:7
|
||||
|
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
| _______^
|
||||
15 | | Spam,
|
||||
16 | | Eggs,
|
||||
17 | | Bar,
|
||||
18 | | Baz,
|
||||
19 | | Spam,
|
||||
20 | | Eggs,
|
||||
21 | | ): ...
|
||||
17 | | Spam,
|
||||
18 | | Eggs,
|
||||
19 | | Bar,
|
||||
20 | | Baz,
|
||||
21 | | Spam,
|
||||
22 | | Eggs,
|
||||
23 | | ): ...
|
||||
| |_^
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
24 |
|
||||
25 | # fmt: on
|
||||
|
|
||||
info: The definition of class `Ham` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:15:5
|
||||
--> src/mdtest_snippet.py:17:5
|
||||
|
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
| ---- Class `Spam` first included in bases list here
|
||||
16 | Eggs,
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
18 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
| ^^^^ Class `Spam` later repeated here
|
||||
20 | Eggs,
|
||||
21 | ): ...
|
||||
22 | Eggs,
|
||||
23 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -160,36 +166,36 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Eggs`
|
||||
--> src/mdtest_snippet.py:14:7
|
||||
--> src/mdtest_snippet.py:16:7
|
||||
|
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
| _______^
|
||||
15 | | Spam,
|
||||
16 | | Eggs,
|
||||
17 | | Bar,
|
||||
18 | | Baz,
|
||||
19 | | Spam,
|
||||
20 | | Eggs,
|
||||
21 | | ): ...
|
||||
17 | | Spam,
|
||||
18 | | Eggs,
|
||||
19 | | Bar,
|
||||
20 | | Baz,
|
||||
21 | | Spam,
|
||||
22 | | Eggs,
|
||||
23 | | ): ...
|
||||
| |_^
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
24 |
|
||||
25 | # fmt: on
|
||||
|
|
||||
info: The definition of class `Ham` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:16:5
|
||||
--> src/mdtest_snippet.py:18:5
|
||||
|
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
16 | Eggs,
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
18 | Eggs,
|
||||
| ---- Class `Eggs` first included in bases list here
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
20 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
22 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
21 | ): ...
|
||||
23 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -197,24 +203,24 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Mushrooms`
|
||||
--> src/mdtest_snippet.py:28:7
|
||||
--> src/mdtest_snippet.py:30:7
|
||||
|
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
31 |
|
||||
32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: The definition of class `Omelette` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:28:28
|
||||
--> src/mdtest_snippet.py:30:28
|
||||
|
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
|
||||
| |
|
||||
| Class `Mushrooms` first included in bases list here
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
31 |
|
||||
32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -222,44 +228,44 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Eggs`
|
||||
--> src/mdtest_snippet.py:35:7
|
||||
--> src/mdtest_snippet.py:37:7
|
||||
|
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
| _______^
|
||||
36 | | Eggs,
|
||||
37 | | Ham,
|
||||
38 | | Spam,
|
||||
39 | | Eggs,
|
||||
40 | | Mushrooms,
|
||||
41 | | Bar,
|
||||
42 | | Eggs,
|
||||
43 | | Baz,
|
||||
38 | | Eggs,
|
||||
39 | | Ham,
|
||||
40 | | Spam,
|
||||
41 | | Eggs,
|
||||
42 | | Mushrooms,
|
||||
43 | | Bar,
|
||||
44 | | Eggs,
|
||||
45 | | ): ...
|
||||
45 | | Baz,
|
||||
46 | | Eggs,
|
||||
47 | | ): ...
|
||||
| |_^
|
||||
46 |
|
||||
47 | # fmt: off
|
||||
48 |
|
||||
49 | # fmt: off
|
||||
|
|
||||
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:36:5
|
||||
--> src/mdtest_snippet.py:38:5
|
||||
|
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | Eggs,
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
38 | Eggs,
|
||||
| ---- Class `Eggs` first included in bases list here
|
||||
37 | Ham,
|
||||
38 | Spam,
|
||||
39 | Eggs,
|
||||
39 | Ham,
|
||||
40 | Spam,
|
||||
41 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
40 | Mushrooms,
|
||||
41 | Bar,
|
||||
42 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
43 | Baz,
|
||||
42 | Mushrooms,
|
||||
43 | Bar,
|
||||
44 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
45 | ): ...
|
||||
45 | Baz,
|
||||
46 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
47 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -267,30 +273,30 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `A`
|
||||
--> src/mdtest_snippet.py:67:7
|
||||
--> src/mdtest_snippet.py:69:7
|
||||
|
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
| _______^
|
||||
68 | | A,
|
||||
69 | | # error: [unused-ignore-comment]
|
||||
70 | | A, # type: ignore[duplicate-base]
|
||||
71 | | ): ...
|
||||
70 | | A,
|
||||
71 | | # error: [unused-ignore-comment]
|
||||
72 | | A, # type: ignore[duplicate-base]
|
||||
73 | | ): ...
|
||||
| |_^
|
||||
72 |
|
||||
73 | # error: [duplicate-base]
|
||||
74 |
|
||||
75 | # error: [duplicate-base]
|
||||
|
|
||||
info: The definition of class `D` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:68:5
|
||||
--> src/mdtest_snippet.py:70:5
|
||||
|
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | A,
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
70 | A,
|
||||
| - Class `A` first included in bases list here
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
| ^ Class `A` later repeated here
|
||||
71 | ): ...
|
||||
73 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -298,42 +304,42 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
--> src/mdtest_snippet.py:70:9
|
||||
--> src/mdtest_snippet.py:72:9
|
||||
|
|
||||
68 | A,
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
70 | A,
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
71 | ): ...
|
||||
73 | ): ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `A`
|
||||
--> src/mdtest_snippet.py:74:7
|
||||
--> src/mdtest_snippet.py:76:7
|
||||
|
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
| _______^
|
||||
75 | | A,
|
||||
76 | | A
|
||||
77 | | ):
|
||||
77 | | A,
|
||||
78 | | A
|
||||
79 | | ):
|
||||
| |_^
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
|
|
||||
info: The definition of class `E` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:75:5
|
||||
--> src/mdtest_snippet.py:77:5
|
||||
|
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | A,
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
77 | A,
|
||||
| - Class `A` first included in bases list here
|
||||
76 | A
|
||||
78 | A
|
||||
| ^ Class `A` later repeated here
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
|
|
@ -341,14 +347,14 @@ info: rule `duplicate-base` is enabled by default
|
|||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
--> src/mdtest_snippet.py:79:13
|
||||
--> src/mdtest_snippet.py:81:13
|
||||
|
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
80 |
|
||||
81 | # fmt: on
|
||||
82 |
|
||||
83 | # fmt: on
|
||||
|
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,120 +13,121 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
|||
|
||||
```
|
||||
1 | from __future__ import annotations
|
||||
2 |
|
||||
3 | class A:
|
||||
4 | def a(self): ...
|
||||
5 | aa: int = 1
|
||||
6 |
|
||||
7 | class B(A):
|
||||
8 | def b(self): ...
|
||||
9 | bb: int = 2
|
||||
10 |
|
||||
11 | class C(B):
|
||||
12 | def c(self): ...
|
||||
13 | cc: int = 3
|
||||
14 |
|
||||
15 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||
16 |
|
||||
17 | super(C, C()).a
|
||||
18 | super(C, C()).b
|
||||
19 | super(C, C()).c # error: [unresolved-attribute]
|
||||
20 |
|
||||
21 | super(B, C()).a
|
||||
22 | super(B, C()).b # error: [unresolved-attribute]
|
||||
23 | super(B, C()).c # error: [unresolved-attribute]
|
||||
24 |
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
26 | super(A, C()).b # error: [unresolved-attribute]
|
||||
27 | super(A, C()).c # error: [unresolved-attribute]
|
||||
28 |
|
||||
29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
30 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||
31 | reveal_type(super(C, C()).aa) # revealed: int
|
||||
32 | reveal_type(super(C, C()).bb) # revealed: int
|
||||
33 | import types
|
||||
34 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||
35 |
|
||||
36 | def f(): ...
|
||||
37 |
|
||||
38 | class Foo[T]:
|
||||
39 | def method(self): ...
|
||||
40 | @property
|
||||
41 | def some_property(self): ...
|
||||
42 |
|
||||
43 | type Alias = int
|
||||
44 |
|
||||
45 | class SomeTypedDict(TypedDict):
|
||||
46 | x: int
|
||||
47 | y: bytes
|
||||
48 |
|
||||
49 | # revealed: <super: <class 'object'>, FunctionType>
|
||||
50 | reveal_type(super(object, f))
|
||||
51 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||
52 | reveal_type(super(object, types.FunctionType.__get__))
|
||||
53 | # revealed: <super: <class 'object'>, GenericAlias>
|
||||
54 | reveal_type(super(object, Foo[int]))
|
||||
55 | # revealed: <super: <class 'object'>, _SpecialForm>
|
||||
56 | reveal_type(super(object, Literal))
|
||||
57 | # revealed: <super: <class 'object'>, TypeAliasType>
|
||||
58 | reveal_type(super(object, Alias))
|
||||
59 | # revealed: <super: <class 'object'>, MethodType>
|
||||
60 | reveal_type(super(object, Foo().method))
|
||||
61 | # revealed: <super: <class 'object'>, property>
|
||||
62 | reveal_type(super(object, Foo.some_property))
|
||||
63 |
|
||||
64 | def g(x: object) -> TypeIs[list[object]]:
|
||||
65 | return isinstance(x, list)
|
||||
66 |
|
||||
67 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
68 | if hasattr(x, "bar"):
|
||||
69 | # revealed: <Protocol with members 'bar'>
|
||||
70 | reveal_type(x)
|
||||
71 | # error: [invalid-super-argument]
|
||||
72 | # revealed: Unknown
|
||||
73 | reveal_type(super(object, x))
|
||||
74 |
|
||||
75 | # error: [invalid-super-argument]
|
||||
76 | # revealed: Unknown
|
||||
77 | reveal_type(super(object, z))
|
||||
78 |
|
||||
79 | is_list = g(x)
|
||||
80 | # revealed: TypeIs[list[object] @ x]
|
||||
81 | reveal_type(is_list)
|
||||
82 | # revealed: <super: <class 'object'>, bool>
|
||||
83 | reveal_type(super(object, is_list))
|
||||
84 |
|
||||
85 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||
86 | reveal_type(super(object, y))
|
||||
87 |
|
||||
88 | # The first argument to `super()` must be an actual class object;
|
||||
89 | # instances of `GenericAlias` are not accepted at runtime:
|
||||
90 | #
|
||||
91 | # error: [invalid-super-argument]
|
||||
92 | # revealed: Unknown
|
||||
93 | reveal_type(super(list[int], []))
|
||||
94 | class Super:
|
||||
95 | def method(self) -> int:
|
||||
96 | return 42
|
||||
97 |
|
||||
98 | class Sub(Super):
|
||||
99 | def method(self: Sub) -> int:
|
||||
100 | # revealed: <super: <class 'Sub'>, Sub>
|
||||
101 | return reveal_type(super(self.__class__, self)).method()
|
||||
2 | from ty_extensions import reveal_mro
|
||||
3 |
|
||||
4 | class A:
|
||||
5 | def a(self): ...
|
||||
6 | aa: int = 1
|
||||
7 |
|
||||
8 | class B(A):
|
||||
9 | def b(self): ...
|
||||
10 | bb: int = 2
|
||||
11 |
|
||||
12 | class C(B):
|
||||
13 | def c(self): ...
|
||||
14 | cc: int = 3
|
||||
15 |
|
||||
16 | reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
17 |
|
||||
18 | super(C, C()).a
|
||||
19 | super(C, C()).b
|
||||
20 | super(C, C()).c # error: [unresolved-attribute]
|
||||
21 |
|
||||
22 | super(B, C()).a
|
||||
23 | super(B, C()).b # error: [unresolved-attribute]
|
||||
24 | super(B, C()).c # error: [unresolved-attribute]
|
||||
25 |
|
||||
26 | super(A, C()).a # error: [unresolved-attribute]
|
||||
27 | super(A, C()).b # error: [unresolved-attribute]
|
||||
28 | super(A, C()).c # error: [unresolved-attribute]
|
||||
29 |
|
||||
30 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
31 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||
32 | reveal_type(super(C, C()).aa) # revealed: int
|
||||
33 | reveal_type(super(C, C()).bb) # revealed: int
|
||||
34 | import types
|
||||
35 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||
36 |
|
||||
37 | def f(): ...
|
||||
38 |
|
||||
39 | class Foo[T]:
|
||||
40 | def method(self): ...
|
||||
41 | @property
|
||||
42 | def some_property(self): ...
|
||||
43 |
|
||||
44 | type Alias = int
|
||||
45 |
|
||||
46 | class SomeTypedDict(TypedDict):
|
||||
47 | x: int
|
||||
48 | y: bytes
|
||||
49 |
|
||||
50 | # revealed: <super: <class 'object'>, FunctionType>
|
||||
51 | reveal_type(super(object, f))
|
||||
52 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||
53 | reveal_type(super(object, types.FunctionType.__get__))
|
||||
54 | # revealed: <super: <class 'object'>, GenericAlias>
|
||||
55 | reveal_type(super(object, Foo[int]))
|
||||
56 | # revealed: <super: <class 'object'>, _SpecialForm>
|
||||
57 | reveal_type(super(object, Literal))
|
||||
58 | # revealed: <super: <class 'object'>, TypeAliasType>
|
||||
59 | reveal_type(super(object, Alias))
|
||||
60 | # revealed: <super: <class 'object'>, MethodType>
|
||||
61 | reveal_type(super(object, Foo().method))
|
||||
62 | # revealed: <super: <class 'object'>, property>
|
||||
63 | reveal_type(super(object, Foo.some_property))
|
||||
64 |
|
||||
65 | def g(x: object) -> TypeIs[list[object]]:
|
||||
66 | return isinstance(x, list)
|
||||
67 |
|
||||
68 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
69 | if hasattr(x, "bar"):
|
||||
70 | # revealed: <Protocol with members 'bar'>
|
||||
71 | reveal_type(x)
|
||||
72 | # error: [invalid-super-argument]
|
||||
73 | # revealed: Unknown
|
||||
74 | reveal_type(super(object, x))
|
||||
75 |
|
||||
76 | # error: [invalid-super-argument]
|
||||
77 | # revealed: Unknown
|
||||
78 | reveal_type(super(object, z))
|
||||
79 |
|
||||
80 | is_list = g(x)
|
||||
81 | # revealed: TypeIs[list[object] @ x]
|
||||
82 | reveal_type(is_list)
|
||||
83 | # revealed: <super: <class 'object'>, bool>
|
||||
84 | reveal_type(super(object, is_list))
|
||||
85 |
|
||||
86 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||
87 | reveal_type(super(object, y))
|
||||
88 |
|
||||
89 | # The first argument to `super()` must be an actual class object;
|
||||
90 | # instances of `GenericAlias` are not accepted at runtime:
|
||||
91 | #
|
||||
92 | # error: [invalid-super-argument]
|
||||
93 | # revealed: Unknown
|
||||
94 | reveal_type(super(list[int], []))
|
||||
95 | class Super:
|
||||
96 | def method(self) -> int:
|
||||
97 | return 42
|
||||
98 |
|
||||
99 | class Sub(Super):
|
||||
100 | def method(self: Sub) -> int:
|
||||
101 | # revealed: <super: <class 'Sub'>, Sub>
|
||||
102 | return reveal_type(super(self.__class__, self)).method()
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'C'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:19:1
|
||||
--> src/mdtest_snippet.py:20:1
|
||||
|
|
||||
17 | super(C, C()).a
|
||||
18 | super(C, C()).b
|
||||
19 | super(C, C()).c # error: [unresolved-attribute]
|
||||
18 | super(C, C()).a
|
||||
19 | super(C, C()).b
|
||||
20 | super(C, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
20 |
|
||||
21 | super(B, C()).a
|
||||
21 |
|
||||
22 | super(B, C()).a
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -134,12 +135,12 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'B'>, C>` has no attribute `b`
|
||||
--> src/mdtest_snippet.py:22:1
|
||||
--> src/mdtest_snippet.py:23:1
|
||||
|
|
||||
21 | super(B, C()).a
|
||||
22 | super(B, C()).b # error: [unresolved-attribute]
|
||||
22 | super(B, C()).a
|
||||
23 | super(B, C()).b # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
23 | super(B, C()).c # error: [unresolved-attribute]
|
||||
24 | super(B, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -147,14 +148,14 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'B'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:23:1
|
||||
--> src/mdtest_snippet.py:24:1
|
||||
|
|
||||
21 | super(B, C()).a
|
||||
22 | super(B, C()).b # error: [unresolved-attribute]
|
||||
23 | super(B, C()).c # error: [unresolved-attribute]
|
||||
22 | super(B, C()).a
|
||||
23 | super(B, C()).b # error: [unresolved-attribute]
|
||||
24 | super(B, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
24 |
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
25 |
|
||||
26 | super(A, C()).a # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -162,14 +163,14 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `a`
|
||||
--> src/mdtest_snippet.py:25:1
|
||||
--> src/mdtest_snippet.py:26:1
|
||||
|
|
||||
23 | super(B, C()).c # error: [unresolved-attribute]
|
||||
24 |
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
24 | super(B, C()).c # error: [unresolved-attribute]
|
||||
25 |
|
||||
26 | super(A, C()).a # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
26 | super(A, C()).b # error: [unresolved-attribute]
|
||||
27 | super(A, C()).c # error: [unresolved-attribute]
|
||||
27 | super(A, C()).b # error: [unresolved-attribute]
|
||||
28 | super(A, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -177,12 +178,12 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `b`
|
||||
--> src/mdtest_snippet.py:26:1
|
||||
--> src/mdtest_snippet.py:27:1
|
||||
|
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
26 | super(A, C()).b # error: [unresolved-attribute]
|
||||
26 | super(A, C()).a # error: [unresolved-attribute]
|
||||
27 | super(A, C()).b # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
27 | super(A, C()).c # error: [unresolved-attribute]
|
||||
28 | super(A, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -190,14 +191,14 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[unresolved-attribute]: Object of type `<super: <class 'A'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:27:1
|
||||
--> src/mdtest_snippet.py:28:1
|
||||
|
|
||||
25 | super(A, C()).a # error: [unresolved-attribute]
|
||||
26 | super(A, C()).b # error: [unresolved-attribute]
|
||||
27 | super(A, C()).c # error: [unresolved-attribute]
|
||||
26 | super(A, C()).a # error: [unresolved-attribute]
|
||||
27 | super(A, C()).b # error: [unresolved-attribute]
|
||||
28 | super(A, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
28 |
|
||||
29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
29 |
|
||||
30 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
|
|
@ -205,14 +206,14 @@ info: rule `unresolved-attribute` is enabled by default
|
|||
|
||||
```
|
||||
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
|
||||
--> src/mdtest_snippet.py:73:21
|
||||
--> src/mdtest_snippet.py:74:21
|
||||
|
|
||||
71 | # error: [invalid-super-argument]
|
||||
72 | # revealed: Unknown
|
||||
73 | reveal_type(super(object, x))
|
||||
72 | # error: [invalid-super-argument]
|
||||
73 | # revealed: Unknown
|
||||
74 | reveal_type(super(object, x))
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
74 |
|
||||
75 | # error: [invalid-super-argument]
|
||||
75 |
|
||||
76 | # error: [invalid-super-argument]
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
|
|
@ -220,14 +221,14 @@ info: rule `invalid-super-argument` is enabled by default
|
|||
|
||||
```
|
||||
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
|
||||
--> src/mdtest_snippet.py:77:17
|
||||
--> src/mdtest_snippet.py:78:17
|
||||
|
|
||||
75 | # error: [invalid-super-argument]
|
||||
76 | # revealed: Unknown
|
||||
77 | reveal_type(super(object, z))
|
||||
76 | # error: [invalid-super-argument]
|
||||
77 | # revealed: Unknown
|
||||
78 | reveal_type(super(object, z))
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
78 |
|
||||
79 | is_list = g(x)
|
||||
79 |
|
||||
80 | is_list = g(x)
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
|
|
@ -235,14 +236,14 @@ info: rule `invalid-super-argument` is enabled by default
|
|||
|
||||
```
|
||||
error[invalid-super-argument]: `types.GenericAlias` instance `list[int]` is not a valid class
|
||||
--> src/mdtest_snippet.py:93:13
|
||||
--> src/mdtest_snippet.py:94:13
|
||||
|
|
||||
91 | # error: [invalid-super-argument]
|
||||
92 | # revealed: Unknown
|
||||
93 | reveal_type(super(list[int], []))
|
||||
92 | # error: [invalid-super-argument]
|
||||
93 | # revealed: Unknown
|
||||
94 | reveal_type(super(list[int], []))
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
94 | class Super:
|
||||
95 | def method(self) -> int:
|
||||
95 | class Super:
|
||||
96 | def method(self) -> int:
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ In type stubs, classes can reference themselves in their base class definitions.
|
|||
`typeshed`, we have `class str(Sequence[str]): ...`.
|
||||
|
||||
```pyi
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Foo[T]: ...
|
||||
|
||||
class Bar(Foo[Bar]): ...
|
||||
|
||||
reveal_type(Bar) # revealed: <class 'Bar'>
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>]
|
||||
reveal_mro(Bar) # revealed: (<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>)
|
||||
```
|
||||
|
||||
## Access to attributes declared in stubs
|
||||
|
|
|
|||
|
|
@ -125,13 +125,14 @@ The stdlib API `os.stat` is a commonly used API that returns an instance of a tu
|
|||
```py
|
||||
import os
|
||||
import stat
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
reveal_type(os.stat("my_file.txt")) # revealed: stat_result
|
||||
reveal_type(os.stat("my_file.txt")[stat.ST_MODE]) # revealed: int
|
||||
reveal_type(os.stat("my_file.txt")[stat.ST_ATIME]) # revealed: int | float
|
||||
|
||||
# revealed: tuple[<class 'stat_result'>, <class 'structseq[int | float]'>, <class 'tuple[int, int, int, int, int, int, int, int | float, int | float, int | float]'>, <class 'Sequence[int | float]'>, <class 'Reversible[int | float]'>, <class 'Collection[int | float]'>, <class 'Iterable[int | float]'>, <class 'Container[int | float]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(os.stat_result.__mro__)
|
||||
# revealed: (<class 'stat_result'>, <class 'structseq[int | float]'>, <class 'tuple[int, int, int, int, int, int, int, int | float, int | float, int | float]'>, <class 'Sequence[int | float]'>, <class 'Reversible[int | float]'>, <class 'Collection[int | float]'>, <class 'Iterable[int | float]'>, <class 'Container[int | float]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(os.stat_result)
|
||||
|
||||
# There are no specific overloads for the `float` elements in `os.stat_result`,
|
||||
# because the fallback `(self, index: SupportsIndex, /) -> int | float` overload
|
||||
|
|
@ -336,15 +337,17 @@ python-version = "3.9"
|
|||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
# revealed: (<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(A)
|
||||
|
||||
class C(tuple): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: (<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
```
|
||||
|
||||
## `typing.Tuple`
|
||||
|
|
@ -376,16 +379,17 @@ python-version = "3.9"
|
|||
|
||||
```py
|
||||
from typing import Tuple
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A(Tuple[int, str]): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
# revealed: (<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(A)
|
||||
|
||||
class C(Tuple): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: (<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
```
|
||||
|
||||
### Union subscript access
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ The `Unknown` type is a special type that we use to represent actually unknown t
|
|||
annotation), as opposed to `Any` which represents an explicitly unknown type.
|
||||
|
||||
```py
|
||||
from ty_extensions import Unknown, static_assert, is_assignable_to
|
||||
from ty_extensions import Unknown, static_assert, is_assignable_to, reveal_mro
|
||||
|
||||
static_assert(is_assignable_to(Unknown, int))
|
||||
static_assert(is_assignable_to(int, Unknown))
|
||||
|
|
@ -107,8 +107,8 @@ def explicit_unknown(x: Unknown, y: tuple[str, Unknown], z: Unknown = 1) -> None
|
|||
```py
|
||||
class C(Unknown): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
# revealed: (<class 'C'>, Unknown, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
|
||||
# error: "Special form `ty_extensions.Unknown` expected no type parameter"
|
||||
u: Unknown[str]
|
||||
|
|
|
|||
|
|
@ -145,10 +145,12 @@ _: type[A, B]
|
|||
## As a base class
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Foo(type[int]): ...
|
||||
|
||||
# TODO: should be `tuple[<class 'Foo'>, <class 'type'>, <class 'object'>]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>]
|
||||
reveal_mro(Foo) # revealed: (<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>)
|
||||
```
|
||||
|
||||
## Display of generic `type[]` types
|
||||
|
|
|
|||
|
|
@ -23,10 +23,11 @@ not a class.
|
|||
|
||||
```py
|
||||
from typing import Type
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class C(Type): ...
|
||||
|
||||
# Runtime value: `(C, type, typing.Generic, object)`
|
||||
# TODO: Add `Generic` to the MRO
|
||||
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'type'>, <class 'object'>]
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'type'>, <class 'object'>)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ python-version = "3.12"
|
|||
|
||||
```py
|
||||
from typing import ClassVar
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
# error: [invalid-type-form] "`ClassVar` annotations are only allowed in class-body scopes"
|
||||
x: ClassVar[int] = 1
|
||||
|
|
@ -155,8 +156,8 @@ def f[T](x: T) -> ClassVar[T]:
|
|||
class Foo(ClassVar[tuple[int]]): ...
|
||||
|
||||
# TODO: Show `Unknown` instead of `@Todo` type in the MRO; or ignore `ClassVar` and show the MRO as if `ClassVar` was not there
|
||||
# revealed: tuple[<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>]
|
||||
reveal_type(Foo.__mro__)
|
||||
# revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
|
||||
reveal_mro(Foo)
|
||||
```
|
||||
|
||||
[`typing.classvar`]: https://docs.python.org/3/library/typing.html#typing.ClassVar
|
||||
|
|
|
|||
|
|
@ -265,6 +265,7 @@ python-version = "3.12"
|
|||
|
||||
```py
|
||||
from typing import Final, ClassVar, Annotated
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
LEGAL_A: Final[int] = 1
|
||||
LEGAL_B: Final = 1
|
||||
|
|
@ -304,8 +305,8 @@ def f[T](x: T) -> Final[T]:
|
|||
class Foo(Final[tuple[int]]): ...
|
||||
|
||||
# TODO: Show `Unknown` instead of `@Todo` type in the MRO; or ignore `Final` and show the MRO as if `Final` was not there
|
||||
# revealed: tuple[<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>]
|
||||
reveal_type(Foo.__mro__)
|
||||
# revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
|
||||
reveal_mro(Foo)
|
||||
```
|
||||
|
||||
### Attribute assignment outside `__init__`
|
||||
|
|
|
|||
|
|
@ -4364,14 +4364,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
|
||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
|
||||
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
||||
);
|
||||
|
||||
if name == "__mro__" {
|
||||
return class_attr_plain;
|
||||
}
|
||||
|
||||
if let Some(enum_class) = match self {
|
||||
Type::ClassLiteral(literal) => Some(literal),
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
|
|
@ -4392,6 +4384,10 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
|
||||
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
|
||||
);
|
||||
|
||||
let class_attr_fallback = Self::try_call_dunder_get_on_attribute(
|
||||
db,
|
||||
class_attr_plain,
|
||||
|
|
|
|||
|
|
@ -1981,11 +1981,6 @@ impl<'db> ClassLiteral<'db> {
|
|||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if name == "__mro__" {
|
||||
let tuple_elements = self.iter_mro(db, specialization);
|
||||
return Place::bound(Type::heterogeneous_tuple(db, tuple_elements)).into();
|
||||
}
|
||||
|
||||
self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,6 +348,27 @@ impl<'db> ClassBase<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display {
|
||||
struct ClassBaseDisplay<'db> {
|
||||
db: &'db dyn Db,
|
||||
base: ClassBase<'db>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClassBaseDisplay<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.base {
|
||||
ClassBase::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
ClassBase::Class(class) => Type::from(class).display(self.db).fmt(f),
|
||||
ClassBase::Protocol => f.write_str("typing.Protocol"),
|
||||
ClassBase::Generic => f.write_str("typing.Generic"),
|
||||
ClassBase::TypedDict => f.write_str("typing.TypedDict"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassBaseDisplay { db, base: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<ClassType<'db>> for ClassBase<'db> {
|
||||
|
|
|
|||
|
|
@ -1319,6 +1319,8 @@ pub enum KnownFunction {
|
|||
HasMember,
|
||||
/// `ty_extensions.reveal_protocol_interface`
|
||||
RevealProtocolInterface,
|
||||
/// `ty_extensions.reveal_mro`
|
||||
RevealMro,
|
||||
/// `ty_extensions.range_constraint`
|
||||
RangeConstraint,
|
||||
/// `ty_extensions.negated_range_constraint`
|
||||
|
|
@ -1397,6 +1399,7 @@ impl KnownFunction {
|
|||
| Self::RevealProtocolInterface
|
||||
| Self::RangeConstraint
|
||||
| Self::NegatedRangeConstraint
|
||||
| Self::RevealMro
|
||||
| Self::AllMembers => module.is_ty_extensions(),
|
||||
Self::ImportModule => module.is_importlib(),
|
||||
|
||||
|
|
@ -1619,6 +1622,85 @@ impl KnownFunction {
|
|||
}
|
||||
}
|
||||
|
||||
KnownFunction::RevealMro => {
|
||||
let [Some(param_type)] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
let mut good_argument = true;
|
||||
let classes = match param_type {
|
||||
Type::ClassLiteral(class) => vec![ClassType::NonGeneric(*class)],
|
||||
Type::GenericAlias(generic_alias) => vec![ClassType::Generic(*generic_alias)],
|
||||
Type::Union(union) => {
|
||||
let elements = union.elements(db);
|
||||
let mut classes = Vec::with_capacity(elements.len());
|
||||
for element in elements {
|
||||
match element {
|
||||
Type::ClassLiteral(class) => {
|
||||
classes.push(ClassType::NonGeneric(*class));
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
classes.push(ClassType::Generic(*generic_alias));
|
||||
}
|
||||
_ => {
|
||||
good_argument = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
classes
|
||||
}
|
||||
_ => {
|
||||
good_argument = false;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
if !good_argument {
|
||||
let Some(builder) =
|
||||
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("Invalid argument to `reveal_mro`");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Can only pass a class object, generic alias or a union thereof"
|
||||
));
|
||||
return;
|
||||
}
|
||||
if let Some(builder) =
|
||||
context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info)
|
||||
{
|
||||
let mut diag = builder.into_diagnostic("Revealed MRO");
|
||||
let span = context.span(&call_expression.arguments.args[0]);
|
||||
let mut message = String::new();
|
||||
for (i, class) in classes.iter().enumerate() {
|
||||
message.push('(');
|
||||
for class in class.iter_mro(db) {
|
||||
message.push_str(&class.display(db).to_string());
|
||||
// Omit the comma for the last element (which is always `object`)
|
||||
if class
|
||||
.into_class()
|
||||
.is_none_or(|base| !base.is_object(context.db()))
|
||||
{
|
||||
message.push_str(", ");
|
||||
}
|
||||
}
|
||||
// If the last element was also the first element
|
||||
// (i.e., it's a length-1 tuple -- which can only happen if we're revealing
|
||||
// the MRO for `object` itself), add a trailing comma so that it's still a
|
||||
// valid tuple display.
|
||||
if class.is_object(db) {
|
||||
message.push(',');
|
||||
}
|
||||
message.push(')');
|
||||
if i < classes.len() - 1 {
|
||||
message.push_str(" | ");
|
||||
}
|
||||
}
|
||||
diag.annotate(Annotation::primary(span).message(message));
|
||||
}
|
||||
}
|
||||
|
||||
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
|
||||
let [Some(first_arg), Some(second_argument)] = parameter_types else {
|
||||
return;
|
||||
|
|
@ -1822,6 +1904,7 @@ pub(crate) mod tests {
|
|||
| KnownFunction::RevealProtocolInterface
|
||||
| KnownFunction::RangeConstraint
|
||||
| KnownFunction::NegatedRangeConstraint
|
||||
| KnownFunction::RevealMro
|
||||
| KnownFunction::AllMembers => KnownModule::TyExtensions,
|
||||
|
||||
KnownFunction::ImportModule => KnownModule::ImportLib,
|
||||
|
|
|
|||
|
|
@ -345,32 +345,24 @@ impl Matcher {
|
|||
return false;
|
||||
};
|
||||
|
||||
// reveal_type
|
||||
if primary_message == "Revealed type"
|
||||
&& primary_annotation == expected_reveal_type_message
|
||||
// reveal_type, reveal_protocol_interface
|
||||
if matches!(
|
||||
primary_message,
|
||||
"Revealed type" | "Revealed protocol interface"
|
||||
) && primary_annotation == expected_reveal_type_message
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// reveal_protocol_interface
|
||||
if primary_message == "Revealed protocol interface"
|
||||
&& primary_annotation == expected_reveal_type_message
|
||||
// reveal_when_assignable_to, reveal_when_subtype_of, reveal_mro
|
||||
if matches!(
|
||||
primary_message,
|
||||
"Assignability holds" | "Subtyping holds" | "Revealed MRO"
|
||||
) && primary_annotation == expected_type
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// reveal_when_assignable_to
|
||||
if primary_message == "Assignability holds"
|
||||
&& primary_annotation == expected_type
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// reveal_when_subtype_of
|
||||
if primary_message == "Subtyping holds" && primary_annotation == expected_type {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# ruff: noqa: PYI021
|
||||
import sys
|
||||
import types
|
||||
from collections.abc import Iterable
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
|
|
@ -115,11 +116,16 @@ def all_members(obj: Any) -> tuple[str, ...]: ...
|
|||
|
||||
# Returns `True` if the given object has a member with the given name.
|
||||
def has_member(obj: Any, name: str) -> bool: ...
|
||||
def reveal_protocol_interface(protocol: type) -> None:
|
||||
"""
|
||||
Passing a protocol type to this function will cause ty to emit an info-level
|
||||
diagnostic describing the protocol's interface.
|
||||
|
||||
# Passing a protocol type to this function will cause ty to emit an info-level
|
||||
# diagnostic describing the protocol's interface. Passing a non-protocol type
|
||||
# will cause ty to emit an error diagnostic.
|
||||
def reveal_protocol_interface(protocol: type) -> None: ...
|
||||
Passing a non-protocol type will cause ty to emit an error diagnostic.
|
||||
"""
|
||||
|
||||
def reveal_mro(cls: type | types.GenericAlias) -> None:
|
||||
"""Reveal the MRO that ty infers for the given class or generic alias."""
|
||||
|
||||
# A protocol describing an interface that should be satisfied by all named tuples
|
||||
# created using `typing.NamedTuple` or `collections.namedtuple`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue