[ty] Fix bug where ty would think all types had an __mro__ attribute (#20995)

This commit is contained in:
Alex Waygood 2025-10-27 11:19:12 +00:00 committed by GitHub
parent 3c7f56f582
commit db0e921db1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 780 additions and 599 deletions

View file

@ -20,6 +20,6 @@ scope-existing-over-new-import,main.py,0,474
scope-prioritize-closer,main.py,0,2 scope-prioritize-closer,main.py,0,2
scope-simple-long-identifier,main.py,0,1 scope-simple-long-identifier,main.py,0,1
tstring-completions,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,0,3
type-var-typing-over-ast,main.py,1,270 type-var-typing-over-ast,main.py,1,270

1 name file index rank
20 scope-prioritize-closer main.py 0 2
21 scope-simple-long-identifier main.py 0 1
22 tstring-completions main.py 0 1
23 ty-extensions-lower-stdlib main.py 0 7 8
24 type-var-typing-over-ast main.py 0 3
25 type-var-typing-over-ast main.py 1 270

View file

@ -1864,7 +1864,7 @@ C.<CURSOR>
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool __instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int __itemsize__ :: int
__module__ :: str __module__ :: str
__mro__ :: tuple[<class 'C'>, <class 'object'>] __mro__ :: tuple[type, ...]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__ __new__ :: def __new__(cls) -> Self@__new__
@ -1933,7 +1933,7 @@ Meta.<CURSOR>
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool __instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
__itemsize__ :: int __itemsize__ :: int
__module__ :: str __module__ :: str
__mro__ :: tuple[<class 'Meta'>, <class 'type'>, <class 'object'>] __mro__ :: tuple[type, ...]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__or__ :: def __or__[Self](self: Self@__or__, value: Any, /) -> UnionType | Self@__or__ __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 __instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int __itemsize__ :: int
__module__ :: str __module__ :: str
__mro__ :: tuple[<class 'Quux'>, <class 'object'>] __mro__ :: tuple[type, ...]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__ __new__ :: def __new__(cls) -> Self@__new__
@ -2138,7 +2138,7 @@ Answer.<CURSOR>
__len__ :: bound method <class 'Answer'>.__len__() -> int __len__ :: bound method <class 'Answer'>.__len__() -> int
__members__ :: MappingProxyType[str, Unknown] __members__ :: MappingProxyType[str, Unknown]
__module__ :: str __module__ :: str
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>] __mro__ :: tuple[type, ...]
__name__ :: str __name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool __ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls, value: object) -> Self@__new__ __new__ :: def __new__(cls, value: object) -> Self@__new__

View file

@ -72,21 +72,23 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
```py ```py
from typing_extensions import Annotated from typing_extensions import Annotated
from ty_extensions import reveal_mro
class C(Annotated[int, "foo"]): ... class C(Annotated[int, "foo"]): ...
# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]` # TODO: Should be `(<class 'C'>, <class 'int'>, <class 'object'>)`
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>] reveal_mro(C) # revealed: (<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>)
``` ```
### Not parameterized ### Not parameterized
```py ```py
from typing_extensions import Annotated from typing_extensions import Annotated
from ty_extensions import reveal_mro
# At runtime, this is an error. # At runtime, this is an error.
# error: [invalid-base] # error: [invalid-base]
class C(Annotated): ... class C(Annotated): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>] reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
``` ```

View file

@ -56,10 +56,11 @@ allowed, even when the unknown superclass is `int`. The assignment to `y` should
```py ```py
from typing import Any from typing import Any
from ty_extensions import reveal_mro
class SubclassOfAny(Any): ... 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] x: SubclassOfAny = 1 # error: [invalid-assignment]
y: int = SubclassOfAny() y: int = SubclassOfAny()

View file

@ -114,6 +114,7 @@ The aliases can be inherited from. Some of these are still partially or wholly T
```py ```py
import typing import typing
from ty_extensions import reveal_mro
#################### ####################
### Built-ins ### Built-ins
@ -121,23 +122,23 @@ import typing
class ListSubclass(typing.List): ... 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'>] # 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_type(ListSubclass.__mro__) reveal_mro(ListSubclass)
class DictSubclass(typing.Dict): ... 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'>] # 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_type(DictSubclass.__mro__) reveal_mro(DictSubclass)
class SetSubclass(typing.Set): ... 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'>] # 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_type(SetSubclass.__mro__) reveal_mro(SetSubclass)
class FrozenSetSubclass(typing.FrozenSet): ... 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'>] # 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_type(FrozenSetSubclass.__mro__) reveal_mro(FrozenSetSubclass)
#################### ####################
### `collections` ### `collections`
@ -145,26 +146,26 @@ reveal_type(FrozenSetSubclass.__mro__)
class ChainMapSubclass(typing.ChainMap): ... 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'>] # 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_type(ChainMapSubclass.__mro__) reveal_mro(ChainMapSubclass)
class CounterSubclass(typing.Counter): ... 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'>] # 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_type(CounterSubclass.__mro__) reveal_mro(CounterSubclass)
class DefaultDictSubclass(typing.DefaultDict): ... 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'>] # 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_type(DefaultDictSubclass.__mro__) reveal_mro(DefaultDictSubclass)
class DequeSubclass(typing.Deque): ... 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'>] # 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_type(DequeSubclass.__mro__) reveal_mro(DequeSubclass)
class OrderedDictSubclass(typing.OrderedDict): ... 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'>] # 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_type(OrderedDictSubclass.__mro__) reveal_mro(OrderedDictSubclass)
``` ```

View file

@ -78,6 +78,7 @@ You can't inherit from most of these. `typing.Callable` is an exception.
```py ```py
from typing import Callable from typing import Callable
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
from ty_extensions import reveal_mro
class A(Self): ... # error: [invalid-base] class A(Self): ... # error: [invalid-base]
class B(Unpack): ... # error: [invalid-base] class B(Unpack): ... # error: [invalid-base]
@ -87,7 +88,7 @@ class E(Concatenate): ... # error: [invalid-base]
class F(Callable): ... class F(Callable): ...
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`" 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 ## Subscriptability

View file

@ -1468,6 +1468,8 @@ C.X = "bar"
### Multiple inheritance ### Multiple inheritance
```py ```py
from ty_extensions import reveal_mro
class O: ... class O: ...
class F(O): class F(O):
@ -1481,8 +1483,8 @@ class C(D, F): ...
class B(E, D): ... class B(E, D): ...
class A(B, C): ... class A(B, C): ...
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>] # revealed: (<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_type(A.__mro__) reveal_mro(A)
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X` # `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] 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 ```py
from typing import ClassVar, Literal from typing import ClassVar, Literal
from ty_extensions import reveal_mro
class A: class A:
x: ClassVar[Literal[1]] = 1 x: ClassVar[Literal[1]] = 1
@ -1689,7 +1692,7 @@ class A:
class B(Any): ... class B(Any): ...
class C(B, A): ... 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 reveal_type(C.x) # revealed: Literal[1] & Any
``` ```

View file

@ -26,6 +26,7 @@ python-version = "3.12"
```py ```py
from __future__ import annotations from __future__ import annotations
from ty_extensions import reveal_mro
class A: class A:
def a(self): ... def a(self): ...
@ -39,7 +40,7 @@ class C(B):
def c(self): ... def c(self): ...
cc: int = 3 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()).a
super(C, C()).b 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. super objects are combined into a union.
```py ```py
from ty_extensions import reveal_mro
class A: ... class A: ...
class B: class B:
@ -429,8 +432,8 @@ class C(A, B): ...
class D(B, A): ... class D(B, A): ...
def f(x: C | D): def f(x: C | D):
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'>)
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>] reveal_mro(D) # revealed: (<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>)
s = super(A, x) s = super(A, x)
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D> reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>

View file

@ -13,6 +13,8 @@ python-version = "3.12"
``` ```
```py ```py
from ty_extensions import reveal_mro
A = int A = int
class G[T]: ... class G[T]: ...
@ -21,5 +23,5 @@ class C(A, G["B"]): ...
A = str A = str
B = bytes 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'>)
``` ```

View file

@ -1173,6 +1173,7 @@ and attributes like the MRO are unchanged:
```py ```py
from dataclasses import dataclass from dataclasses import dataclass
from ty_extensions import reveal_mro
@dataclass @dataclass
class Person: class Person:
@ -1180,7 +1181,8 @@ class Person:
age: int | None = None age: int | None = None
reveal_type(type(Person)) # revealed: <class 'type'> 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: The generated methods have the following signatures:

View file

@ -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. `ParamSpec`s or `TypeVarTuple`s after the class name.
```py ```py
from ty_extensions import generic_context from ty_extensions import generic_context, reveal_mro
class SingleTypevar[T]: ... class SingleTypevar[T]: ...
class MultipleTypevars[T, S]: ... 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" # error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
class BothGenericSyntaxes[U](Generic[T]): ... 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-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
# error: [invalid-base] "Cannot inherit from plain `Generic`" # error: [invalid-base] "Cannot inherit from plain `Generic`"
class DoublyInvalid[T](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`: Generic classes implicitly inherit from `Generic`:
@ -91,26 +91,26 @@ Generic classes implicitly inherit from `Generic`:
```py ```py
class Foo[T]: ... class Foo[T]: ...
# revealed: tuple[<class 'Foo[Unknown]'>, typing.Generic, <class 'object'>] # revealed: (<class 'Foo[Unknown]'>, typing.Generic, <class 'object'>)
reveal_type(Foo.__mro__) reveal_mro(Foo)
# revealed: tuple[<class 'Foo[int]'>, typing.Generic, <class 'object'>] # revealed: (<class 'Foo[int]'>, typing.Generic, <class 'object'>)
reveal_type(Foo[int].__mro__) reveal_mro(Foo[int])
class A: ... class A: ...
class Bar[T](A): ... class Bar[T](A): ...
# revealed: tuple[<class 'Bar[Unknown]'>, <class 'A'>, typing.Generic, <class 'object'>] # revealed: (<class 'Bar[Unknown]'>, <class 'A'>, typing.Generic, <class 'object'>)
reveal_type(Bar.__mro__) reveal_mro(Bar)
# revealed: tuple[<class 'Bar[int]'>, <class 'A'>, typing.Generic, <class 'object'>] # revealed: (<class 'Bar[int]'>, <class 'A'>, typing.Generic, <class 'object'>)
reveal_type(Bar[int].__mro__) reveal_mro(Bar[int])
class B: ... class B: ...
class Baz[T](A, B): ... class Baz[T](A, B): ...
# revealed: tuple[<class 'Baz[Unknown]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>] # revealed: (<class 'Baz[Unknown]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>)
reveal_type(Baz.__mro__) reveal_mro(Baz)
# revealed: tuple[<class 'Baz[int]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>] # revealed: (<class 'Baz[int]'>, <class 'A'>, <class 'B'>, typing.Generic, <class 'object'>)
reveal_type(Baz[int].__mro__) reveal_mro(Baz[int])
``` ```
## Specializing generic classes explicitly ## Specializing generic classes explicitly

View file

@ -67,22 +67,25 @@ x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]"
`a.py`: `a.py`:
```py ```py
from ty_extensions import reveal_mro
class A: ... class A: ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'object'>] reveal_mro(A) # revealed: (<class 'A'>, <class 'object'>)
import b import b
class C(b.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`: `b.py`:
```py ```py
from ty_extensions import reveal_mro
from a import A from a import A
class B(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'>)
``` ```

View file

@ -798,11 +798,11 @@ A class literal can be iterated over if it has `Any` or `Unknown` in its MRO, si
```py ```py
from unresolved_module import SomethingUnknown # error: [unresolved-import] from unresolved_module import SomethingUnknown # error: [unresolved-import]
from typing import Any, Iterable 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): ... 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 # TODO: these should pass
static_assert(is_assignable_to(TypeOf[Foo], Iterable[Unknown])) # error: [static-assert-error] static_assert(is_assignable_to(TypeOf[Foo], Iterable[Unknown])) # error: [static-assert-error]
@ -815,7 +815,7 @@ for x in Foo:
class Bar(Any): ... 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 # TODO: these should pass
static_assert(is_assignable_to(TypeOf[Bar], Iterable[Any])) # error: [static-assert-error] static_assert(is_assignable_to(TypeOf[Bar], Iterable[Any])) # error: [static-assert-error]

View file

@ -1,55 +1,74 @@
# Method Resolution Order tests # 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 It's extremely important for us to know the precise possible values of a class's Method Resolution
know the precise possible values of a class's Method Resolution Order, or we won't be able to infer Order, or we won't be able to infer the correct type of attributes accessed from instances.
the correct type of attributes accessed from instances.
For documentation on method resolution orders, see: For documentation on method resolution orders, see:
- <https://docs.python.org/3/glossary.html#term-method-resolution-order> - <https://docs.python.org/3/glossary.html#term-method-resolution-order>
- <https://docs.python.org/3/howto/mro.html#python-2-3-mro> - <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 ## No bases
```py ```py
from ty_extensions import reveal_mro
class C: ... 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 ## The special case: `object` itself
```py ```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` ## Explicit inheritance from `object`
```py ```py
from ty_extensions import reveal_mro
class C(object): ... 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 ## Explicit inheritance from non-`object` single base
```py ```py
from ty_extensions import reveal_mro
class A: ... class A: ...
class B(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 ## Linearization of multiple bases
```py ```py
from ty_extensions import reveal_mro
class A: ... class A: ...
class B: ... class B: ...
class C(A, 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) ## 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> This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
```py ```py
from ty_extensions import reveal_mro
class O: ... class O: ...
class X(O): ... class X(O): ...
class Y(O): ... class Y(O): ...
class A(X, Y): ... class A(X, Y): ...
class B(Y, X): ... class B(Y, X): ...
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'>)
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>] reveal_mro(B) # revealed: (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
``` ```
## Complex diamond inheritance (2) ## 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> This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
```py ```py
from ty_extensions import reveal_mro
class O: ... class O: ...
class F(O): ... class F(O): ...
class E(O): ... class E(O): ...
@ -80,12 +103,12 @@ class C(D, F): ...
class B(D, E): ... class B(D, E): ...
class A(B, C): ... class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>] # revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_type(C.__mro__) reveal_mro(C)
# revealed: tuple[<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>] # revealed: (<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>)
reveal_type(B.__mro__) reveal_mro(B)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>] # revealed: (<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_type(A.__mro__) reveal_mro(A)
``` ```
## Complex diamond inheritance (3) ## 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> This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
```py ```py
from ty_extensions import reveal_mro
class O: ... class O: ...
class F(O): ... class F(O): ...
class E(O): ... class E(O): ...
@ -101,12 +126,12 @@ class C(D, F): ...
class B(E, D): ... class B(E, D): ...
class A(B, C): ... class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>] # revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_type(C.__mro__) reveal_mro(C)
# revealed: tuple[<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>] # revealed: (<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>)
reveal_type(B.__mro__) reveal_mro(B)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>] # revealed: (<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_type(A.__mro__) reveal_mro(A)
``` ```
## Complex diamond inheritance (4) ## 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> This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
```py ```py
from ty_extensions import reveal_mro
class O: ... class O: ...
class A(O): ... class A(O): ...
class B(O): ... class B(O): ...
@ -125,19 +152,20 @@ class K2(D, B, E): ...
class K3(D, A): ... class K3(D, A): ...
class Z(K1, K2, K3): ... class Z(K1, K2, K3): ...
# revealed: tuple[<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>] # revealed: (<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>)
reveal_type(K1.__mro__) reveal_mro(K1)
# revealed: tuple[<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>] # revealed: (<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>)
reveal_type(K2.__mro__) reveal_mro(K2)
# revealed: tuple[<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>] # revealed: (<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>)
reveal_type(K3.__mro__) reveal_mro(K3)
# revealed: tuple[<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>] # revealed: (<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__) reveal_mro(Z)
``` ```
## Inheritance from `Unknown` ## Inheritance from `Unknown`
```py ```py
from ty_extensions import reveal_mro
from does_not_exist import DoesNotExist # error: [unresolved-import] from does_not_exist import DoesNotExist # error: [unresolved-import]
class A(DoesNotExist): ... class A(DoesNotExist): ...
@ -147,11 +175,11 @@ class D(A, B, C): ...
class E(B, C): ... class E(B, C): ...
class F(E, A): ... class F(E, A): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, Unknown, <class 'object'>] reveal_mro(A) # revealed: (<class 'A'>, Unknown, <class 'object'>)
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>] reveal_mro(D) # revealed: (<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'>] reveal_mro(E) # revealed: (<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>)
# revealed: tuple[<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>] # revealed: (<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>)
reveal_type(F.__mro__) reveal_mro(F)
``` ```
## Inheritance with intersections that include `Unknown` ## 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`. disjoint from `type`.
```py ```py
from ty_extensions import reveal_mro
from does_not_exist import DoesNotExist # error: [unresolved-import] from does_not_exist import DoesNotExist # error: [unresolved-import]
reveal_type(DoesNotExist) # revealed: Unknown reveal_type(DoesNotExist) # revealed: Unknown
if hasattr(DoesNotExist, "__mro__"): if hasattr(DoesNotExist, "__mro__"):
# TODO: this should be `Unknown & <Protocol with members '__mro__'>` or similar reveal_type(DoesNotExist) # revealed: Unknown & <Protocol with members '__mro__'>
# (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
class Foo(DoesNotExist): ... # no error! 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): if not isinstance(DoesNotExist, type):
reveal_type(DoesNotExist) # revealed: Unknown & ~type reveal_type(DoesNotExist) # revealed: Unknown & ~type
class Foo(DoesNotExist): ... # error: [unsupported-base] 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]` ## Inheritance from `type[Any]` and `type[Unknown]`
@ -186,14 +213,14 @@ guarantee:
```py ```py
from typing import Any 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]]): def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
class Foo(x): ... 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): ... 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 ## `__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]`: creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
```py ```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'>]`" # 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): ... 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): ... 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" # This is the `TypeError` at the bottom of "ex_2"
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end> # 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 A(X, Y): ...
class B(Y, X): ... class B(Y, X): ...
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'>)
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <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'>]`" # 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): ... 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): ... 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` ## `__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. `[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
```py ```py
from ty_extensions import reveal_mro
def returns_bool() -> bool: def returns_bool() -> bool:
return True 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'>`" # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
class Foo(x): ... 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 ## `__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 ```py
from typing import Any from typing import Any
from ty_extensions import reveal_mro
def _(flag: bool, any: Any): def _(flag: bool, any: Any):
if flag: if flag:
@ -276,12 +308,14 @@ def _(flag: bool, any: Any):
class Base: ... class Base: ...
class Foo(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 ## `__bases__` includes multiple `Union`s
```py ```py
from ty_extensions import reveal_mro
def returns_bool() -> bool: def returns_bool() -> bool:
return True 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'>`" # error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
class Foo(x, y): ... 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 ## `__bases__` lists that cause errors... now with `Union`s
```py ```py
from ty_extensions import reveal_mro
def returns_bool() -> bool: def returns_bool() -> bool:
return True return True
@ -328,11 +364,11 @@ else:
# error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`" # error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
class PossibleError(foo, X): ... 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): ... 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(): if returns_bool():
class B(X, Y): ... class B(X, Y): ...
@ -340,13 +376,13 @@ if returns_bool():
else: else:
class B(Y, X): ... 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'>] # revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
reveal_type(B.__mro__) reveal_mro(B)
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`" # error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
class Z(A, 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` ## `__bases__` lists that include objects that are not instances of `type`
@ -389,9 +425,11 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
<!-- snapshot-diagnostics --> <!-- snapshot-diagnostics -->
```py ```py
from ty_extensions import reveal_mro
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`" 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 Spam: ...
class Eggs: ... class Eggs: ...
@ -413,12 +451,12 @@ class Ham(
# fmt: on # 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 Mushrooms: ...
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base] 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 # 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. the dynamic base can usually be materialised to a type that would lead to a resolvable MRO.
```py ```py
from ty_extensions import reveal_mro
from unresolvable_module import UnknownBase1, UnknownBase2 # error: [unresolved-import] from unresolvable_module import UnknownBase1, UnknownBase2 # error: [unresolved-import]
reveal_type(UnknownBase1) # revealed: Unknown reveal_type(UnknownBase1) # revealed: Unknown
@ -502,7 +541,7 @@ reveal_type(UnknownBase2) # revealed: Unknown
# no error here -- we respect the gradual guarantee: # no error here -- we respect the gradual guarantee:
class Foo(UnknownBase1, UnknownBase2): ... 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 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`" # error: [duplicate-base] "Duplicate base class `Foo`"
class Bar(UnknownBase1, Foo, UnknownBase2, 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 ## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
@ -529,25 +568,26 @@ reveal_type(unknown_object.__mro__) # revealed: Unknown
```py ```py
from typing import Generic, TypeVar, Iterator from typing import Generic, TypeVar, Iterator
from ty_extensions import reveal_mro
T = TypeVar("T") T = TypeVar("T")
class peekable(Generic[T], Iterator[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'>] # revealed: (<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(peekable.__mro__) reveal_mro(peekable)
class peekable2(Iterator[T], Generic[T]): ... 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'>] # revealed: (<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(peekable2.__mro__) reveal_mro(peekable2)
class Base: ... class Base: ...
class Intermediate(Base, Generic[T]): ... class Intermediate(Base, Generic[T]): ...
class Sub(Intermediate[T], Base): ... class Sub(Intermediate[T], Base): ...
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>] # revealed: (<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>)
reveal_type(Sub.__mro__) reveal_mro(Sub)
``` ```
## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases ## 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. These are invalid, but we need to be able to handle them gracefully without panicking.
```pyi ```pyi
from ty_extensions import reveal_mro
class Foo(Foo): ... # error: [cyclic-class-definition] class Foo(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo) # revealed: <class 'Foo'> 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 Bar: ...
class Baz: ... class Baz: ...
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition] class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
reveal_type(Boz) # revealed: <class 'Boz'> 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 ## 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: These are similarly unlikely, but we still shouldn't crash:
```pyi ```pyi
from ty_extensions import reveal_mro
class Foo(Bar): ... # error: [cyclic-class-definition] class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition] class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo): ... # error: [cyclic-class-definition] class Baz(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>] reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>] reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>] reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
``` ```
## Classes with cycles in their MROs, and multiple inheritance ## Classes with cycles in their MROs, and multiple inheritance
```pyi ```pyi
from ty_extensions import reveal_mro
class Spam: ... class Spam: ...
class Foo(Bar): ... # error: [cyclic-class-definition] class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition] class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo, Spam): ... # error: [cyclic-class-definition] class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>] reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>] reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>] reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
``` ```
## Classes with cycles in their MRO, and a sub-graph ## Classes with cycles in their MRO, and a sub-graph
```pyi ```pyi
from ty_extensions import reveal_mro
class FooCycle(BarCycle): ... # error: [cyclic-class-definition] class FooCycle(BarCycle): ... # error: [cyclic-class-definition]
class Foo: ... class Foo: ...
class BarCycle(FooCycle): ... # error: [cyclic-class-definition] class BarCycle(FooCycle): ... # error: [cyclic-class-definition]
@ -622,10 +670,10 @@ class Bar(Foo): ...
class Baz(Bar, BarCycle): ... class Baz(Bar, BarCycle): ...
class Spam(Baz): ... class Spam(Baz): ...
reveal_type(FooCycle.__mro__) # revealed: tuple[<class 'FooCycle'>, Unknown, <class 'object'>] reveal_mro(FooCycle) # revealed: (<class 'FooCycle'>, Unknown, <class 'object'>)
reveal_type(BarCycle.__mro__) # revealed: tuple[<class 'BarCycle'>, Unknown, <class 'object'>] reveal_mro(BarCycle) # revealed: (<class 'BarCycle'>, Unknown, <class 'object'>)
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>] reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
reveal_type(Spam.__mro__) # revealed: tuple[<class 'Spam'>, Unknown, <class 'object'>] reveal_mro(Spam) # revealed: (<class 'Spam'>, Unknown, <class 'object'>)
``` ```
## Other classes with possible cycles ## Other classes with possible cycles
@ -636,20 +684,22 @@ python-version = "3.13"
``` ```
```pyi ```pyi
from ty_extensions import reveal_mro
class C(C.a): ... class C(C.a): ...
reveal_type(C.__class__) # revealed: <class 'type'> 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): class D(D.a):
a: D a: D
reveal_type(D.__class__) # revealed: <class 'type'> 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): ... class E[T](E.a): ...
reveal_type(E.__class__) # revealed: <class 'type'> 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] class F[T](F(), F): ... # error: [cyclic-class-definition]
reveal_type(F.__class__) # revealed: type[Unknown] 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'>)
``` ```

View file

@ -9,7 +9,7 @@ name, and not just by its numeric position within the tuple:
```py ```py
from typing import NamedTuple 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): class Person(NamedTuple):
id: int id: int
@ -25,8 +25,8 @@ reveal_type(alice.id) # revealed: int
reveal_type(alice.name) # revealed: str reveal_type(alice.name) # revealed: str
reveal_type(alice.age) # revealed: int | None 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'>] # 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_type(Person.__mro__) reveal_mro(Person)
static_assert(is_subtype_of(Person, tuple[int, str, int | None])) static_assert(is_subtype_of(Person, tuple[int, str, int | None]))
static_assert(is_subtype_of(Person, tuple[object, ...])) 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.__qualname__) # revealed: str
reveal_type(typing.NamedTuple.__kwdefaults__) # revealed: dict[str, Any] | None 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), # error: [unresolved-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: Unknown
reveal_type(typing.NamedTuple.__mro__) # revealed: tuple[<class 'FunctionType'>, <class 'object'>]
``` ```
By the normal rules, `NamedTuple` and `type[NamedTuple]` should not be valid in type expressions -- By the normal rules, `NamedTuple` and `type[NamedTuple]` should not be valid in type expressions --

View file

@ -25,10 +25,11 @@ A protocol is defined by inheriting from the `Protocol` class, which is annotate
```py ```py
from typing import Protocol from typing import Protocol
from ty_extensions import reveal_mro
class MyProtocol(Protocol): ... 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 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 ```py
class Foo(Protocol, Protocol): ... # error: [duplicate-base] 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 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, # Note that this class definition *will* actually succeed at runtime,
# unlike classes that combine PEP-695 type parameters with inheritance from `Generic[]` # 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 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] class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
x: T x: T
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>] # revealed: (<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>)
reveal_type(DuplicateBases.__mro__) reveal_mro(DuplicateBases)
``` ```
The introspection helper `typing(_extensions).is_protocol` can be used to verify whether a class is 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 ```py
class SubclassOfMyProtocol(MyProtocol): ... class SubclassOfMyProtocol(MyProtocol): ...
# revealed: tuple[<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: (<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(SubclassOfMyProtocol.__mro__) reveal_mro(SubclassOfMyProtocol)
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False] reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False]
``` ```
@ -143,8 +144,8 @@ class OtherProtocol(Protocol):
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ... class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
# revealed: tuple[<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: (<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(ComplexInheritance.__mro__) reveal_mro(ComplexInheritance)
reveal_type(is_protocol(ComplexInheritance)) # revealed: Literal[True] 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`" # error: [invalid-protocol] "Protocol class `Invalid` cannot inherit from non-protocol class `NotAProtocol`"
class Invalid(NotAProtocol, Protocol): ... class Invalid(NotAProtocol, Protocol): ...
# revealed: tuple[<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: (<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(Invalid.__mro__) reveal_mro(Invalid)
# error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`" # error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`"
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ... class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
# revealed: tuple[<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: (<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(AlsoInvalid.__mro__) reveal_mro(AlsoInvalid)
class NotAGenericProtocol[T]: ... class NotAGenericProtocol[T]: ...
# error: [invalid-protocol] "Protocol class `StillInvalid` cannot inherit from non-protocol class `NotAGenericProtocol`" # error: [invalid-protocol] "Protocol class `StillInvalid` cannot inherit from non-protocol class `NotAGenericProtocol`"
class StillInvalid(NotAGenericProtocol[int], Protocol): ... class StillInvalid(NotAGenericProtocol[int], Protocol): ...
# revealed: tuple[<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>] # revealed: (<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_type(StillInvalid.__mro__) reveal_mro(StillInvalid)
``` ```
But two exceptions to this rule are `object` and `Generic`: But two exceptions to this rule are `object` and `Generic`:
@ -188,7 +189,7 @@ T = TypeVar("T")
# type checkers. # type checkers.
class Fine(Protocol, object): ... 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 StillFine(Protocol, Generic[T], object): ...
class EvenThis[T](Protocol, 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 ```py
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ... class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
# revealed: tuple[<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>] # revealed: (<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>)
reveal_type(FineAndDandy.__mro__) reveal_mro(FineAndDandy)
``` ```
But if `Protocol` is not present in the bases list, the resulting class doesn't count as a protocol 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 ```py
import abc import abc
import typing import typing
from ty_extensions import TypeOf from ty_extensions import TypeOf, reveal_mro
reveal_type(type(Protocol)) # revealed: <class '_ProtocolMeta'> reveal_type(type(Protocol)) # revealed: <class '_ProtocolMeta'>
# revealed: tuple[<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>] # revealed: (<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>)
reveal_type(type(Protocol).__mro__) reveal_mro(type(Protocol))
static_assert(is_subtype_of(TypeOf[Protocol], type)) static_assert(is_subtype_of(TypeOf[Protocol], type))
static_assert(is_subtype_of(TypeOf[Protocol], abc.ABCMeta)) static_assert(is_subtype_of(TypeOf[Protocol], abc.ABCMeta))
static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta)) static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta))

View file

@ -12,36 +12,38 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py ## mdtest_snippet.py
``` ```
1 | def returns_bool() -> bool: 1 | from ty_extensions import reveal_mro
2 | return True 2 |
3 | 3 | def returns_bool() -> bool:
4 | class A: ... 4 | return True
5 | class B: ... 5 |
6 | 6 | class A: ...
7 | if returns_bool(): 7 | class B: ...
8 | x = A 8 |
9 | else: 9 | if returns_bool():
10 | x = B 10 | x = A
11 | 11 | else:
12 | reveal_type(x) # revealed: <class 'A'> | <class 'B'> 12 | x = B
13 | 13 |
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`" 14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
15 | class Foo(x): ... 15 |
16 | 16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>] 17 | class Foo(x): ...
18 |
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
``` ```
# Diagnostics # Diagnostics
``` ```
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>` 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'>`" 16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
15 | class Foo(x): ... 17 | class Foo(x): ...
| ^ | ^
16 | 18 |
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>] 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: ty cannot resolve a consistent MRO for class `Foo` due to this base
info: Only class objects or `Any` are supported as class bases info: Only class objects or `Any` are supported as class bases

View file

@ -12,109 +12,115 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py ## mdtest_snippet.py
``` ```
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`" 1 | from ty_extensions import reveal_mro
2 | 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 | 4 |
5 | class Spam: ... 5 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
6 | class Eggs: ... 6 |
7 | class Bar: ... 7 | class Spam: ...
8 | class Baz: ... 8 | class Eggs: ...
9 | 9 | class Bar: ...
10 | # fmt: off 10 | class Baz: ...
11 | 11 |
12 | # error: [duplicate-base] "Duplicate base class `Spam`" 12 | # fmt: off
13 | # error: [duplicate-base] "Duplicate base class `Eggs`" 13 |
14 | class Ham( 14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | Spam, 15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | Eggs, 16 | class Ham(
17 | Bar, 17 | Spam,
18 | Baz, 18 | Eggs,
19 | Spam, 19 | Bar,
20 | Eggs, 20 | Baz,
21 | ): ... 21 | Spam,
22 | 22 | Eggs,
23 | # fmt: on 23 | ): ...
24 | 24 |
25 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>] 25 | # fmt: on
26 | 26 |
27 | class Mushrooms: ... 27 | reveal_mro(Ham) # revealed: (<class 'Ham'>, Unknown, <class 'object'>)
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base] 28 |
29 | 29 | class Mushrooms: ...
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>] 30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 | 31 |
32 | # fmt: off 32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
33 | 33 |
34 | # error: [duplicate-base] "Duplicate base class `Eggs`" 34 | # fmt: off
35 | class VeryEggyOmelette( 35 |
36 | Eggs, 36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | Ham, 37 | class VeryEggyOmelette(
38 | Spam, 38 | Eggs,
39 | Eggs, 39 | Ham,
40 | Mushrooms, 40 | Spam,
41 | Bar, 41 | Eggs,
42 | Eggs, 42 | Mushrooms,
43 | Baz, 43 | Bar,
44 | Eggs, 44 | Eggs,
45 | ): ... 45 | Baz,
46 | 46 | Eggs,
47 | # fmt: off 47 | ): ...
48 | # fmt: off 48 |
49 | 49 | # fmt: off
50 | class A: ... 50 | # fmt: off
51 | 51 |
52 | class B( # type: ignore[duplicate-base] 52 | class A: ...
53 | A, 53 |
54 | A, 54 | class B( # type: ignore[duplicate-base]
55 | ): ... 55 | A,
56 | 56 | A,
57 | class C( 57 | ): ...
58 | A, 58 |
59 | A 59 | class C(
60 | ): # type: ignore[duplicate-base] 60 | A,
61 | x: int 61 | A
62 | 62 | ): # type: ignore[duplicate-base]
63 | # fmt: on 63 | x: int
64 | # fmt: off 64 |
65 | 65 | # fmt: on
66 | # error: [duplicate-base] 66 | # fmt: off
67 | class D( 67 |
68 | A, 68 | # error: [duplicate-base]
69 | # error: [unused-ignore-comment] 69 | class D(
70 | A, # type: ignore[duplicate-base] 70 | A,
71 | ): ... 71 | # error: [unused-ignore-comment]
72 | 72 | A, # type: ignore[duplicate-base]
73 | # error: [duplicate-base] 73 | ): ...
74 | class E( 74 |
75 | A, 75 | # error: [duplicate-base]
76 | A 76 | class E(
77 | ): 77 | A,
78 | # error: [unused-ignore-comment] 78 | A
79 | x: int # type: ignore[duplicate-base] 79 | ):
80 | 80 | # error: [unused-ignore-comment]
81 | # fmt: on 81 | x: int # type: ignore[duplicate-base]
82 |
83 | # fmt: on
``` ```
# Diagnostics # Diagnostics
``` ```
error[duplicate-base]: Duplicate base class `str` 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 | 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 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` later repeated here
| | | |
| Class `str` first included in bases list here | Class `str` first included in bases list here
2 | 4 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>] 5 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
| |
info: rule `duplicate-base` is enabled by default 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` 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`" 14 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`" 15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham( 16 | class Ham(
| _______^ | _______^
15 | | Spam, 17 | | Spam,
16 | | Eggs, 18 | | Eggs,
17 | | Bar, 19 | | Bar,
18 | | Baz, 20 | | Baz,
19 | | Spam, 21 | | Spam,
20 | | Eggs, 22 | | Eggs,
21 | | ): ... 23 | | ): ...
| |_^ | |_^
22 | 24 |
23 | # fmt: on 25 | # fmt: on
| |
info: The definition of class `Ham` will raise `TypeError` at runtime 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`" 15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham( 16 | class Ham(
15 | Spam, 17 | Spam,
| ---- Class `Spam` first included in bases list here | ---- Class `Spam` first included in bases list here
16 | Eggs, 18 | Eggs,
17 | Bar, 19 | Bar,
18 | Baz, 20 | Baz,
19 | Spam, 21 | Spam,
| ^^^^ Class `Spam` later repeated here | ^^^^ Class `Spam` later repeated here
20 | Eggs, 22 | Eggs,
21 | ): ... 23 | ): ...
| |
info: rule `duplicate-base` is enabled by default 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` 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`" 14 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`" 15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham( 16 | class Ham(
| _______^ | _______^
15 | | Spam, 17 | | Spam,
16 | | Eggs, 18 | | Eggs,
17 | | Bar, 19 | | Bar,
18 | | Baz, 20 | | Baz,
19 | | Spam, 21 | | Spam,
20 | | Eggs, 22 | | Eggs,
21 | | ): ... 23 | | ): ...
| |_^ | |_^
22 | 24 |
23 | # fmt: on 25 | # fmt: on
| |
info: The definition of class `Ham` will raise `TypeError` at runtime 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( 16 | class Ham(
15 | Spam, 17 | Spam,
16 | Eggs, 18 | Eggs,
| ---- Class `Eggs` first included in bases list here | ---- Class `Eggs` first included in bases list here
17 | Bar, 19 | Bar,
18 | Baz, 20 | Baz,
19 | Spam, 21 | Spam,
20 | Eggs, 22 | Eggs,
| ^^^^ Class `Eggs` later repeated here | ^^^^ Class `Eggs` later repeated here
21 | ): ... 23 | ): ...
| |
info: rule `duplicate-base` is enabled by default 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` error[duplicate-base]: Duplicate base class `Mushrooms`
--> src/mdtest_snippet.py:28:7 --> src/mdtest_snippet.py:30:7
| |
27 | class Mushrooms: ... 29 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base] 30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 | 31 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>] 32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
| |
info: The definition of class `Omelette` will raise `TypeError` at runtime 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: ... 29 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base] 30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here | --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
| | | |
| Class `Mushrooms` first included in bases list here | Class `Mushrooms` first included in bases list here
29 | 31 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>] 32 | reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
| |
info: rule `duplicate-base` is enabled by default 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` 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`" 36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette( 37 | class VeryEggyOmelette(
| _______^ | _______^
36 | | Eggs, 38 | | Eggs,
37 | | Ham, 39 | | Ham,
38 | | Spam, 40 | | Spam,
39 | | Eggs, 41 | | Eggs,
40 | | Mushrooms, 42 | | Mushrooms,
41 | | Bar, 43 | | Bar,
42 | | Eggs,
43 | | Baz,
44 | | Eggs, 44 | | Eggs,
45 | | ): ... 45 | | Baz,
46 | | Eggs,
47 | | ): ...
| |_^ | |_^
46 | 48 |
47 | # fmt: off 49 | # fmt: off
| |
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime 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`" 36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette( 37 | class VeryEggyOmelette(
36 | Eggs, 38 | Eggs,
| ---- Class `Eggs` first included in bases list here | ---- Class `Eggs` first included in bases list here
37 | Ham, 39 | Ham,
38 | Spam, 40 | Spam,
39 | Eggs, 41 | Eggs,
| ^^^^ Class `Eggs` later repeated here | ^^^^ Class `Eggs` later repeated here
40 | Mushrooms, 42 | Mushrooms,
41 | Bar, 43 | Bar,
42 | Eggs,
| ^^^^ Class `Eggs` later repeated here
43 | Baz,
44 | Eggs, 44 | Eggs,
| ^^^^ Class `Eggs` later repeated here | ^^^^ Class `Eggs` later repeated here
45 | ): ... 45 | Baz,
46 | Eggs,
| ^^^^ Class `Eggs` later repeated here
47 | ): ...
| |
info: rule `duplicate-base` is enabled by default 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` error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:67:7 --> src/mdtest_snippet.py:69:7
| |
66 | # error: [duplicate-base] 68 | # error: [duplicate-base]
67 | class D( 69 | class D(
| _______^ | _______^
68 | | A, 70 | | A,
69 | | # error: [unused-ignore-comment] 71 | | # error: [unused-ignore-comment]
70 | | A, # type: ignore[duplicate-base] 72 | | A, # type: ignore[duplicate-base]
71 | | ): ... 73 | | ): ...
| |_^ | |_^
72 | 74 |
73 | # error: [duplicate-base] 75 | # error: [duplicate-base]
| |
info: The definition of class `D` will raise `TypeError` at runtime 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] 68 | # error: [duplicate-base]
67 | class D( 69 | class D(
68 | A, 70 | A,
| - Class `A` first included in bases list here | - Class `A` first included in bases list here
69 | # error: [unused-ignore-comment] 71 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base] 72 | A, # type: ignore[duplicate-base]
| ^ Class `A` later repeated here | ^ Class `A` later repeated here
71 | ): ... 73 | ): ...
| |
info: rule `duplicate-base` is enabled by default info: rule `duplicate-base` is enabled by default
@ -298,42 +304,42 @@ info: rule `duplicate-base` is enabled by default
``` ```
info[unused-ignore-comment] info[unused-ignore-comment]
--> src/mdtest_snippet.py:70:9 --> src/mdtest_snippet.py:72:9
| |
68 | A, 70 | A,
69 | # error: [unused-ignore-comment] 71 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base] 72 | A, # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
71 | ): ... 73 | ): ...
| |
``` ```
``` ```
error[duplicate-base]: Duplicate base class `A` error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:74:7 --> src/mdtest_snippet.py:76:7
| |
73 | # error: [duplicate-base] 75 | # error: [duplicate-base]
74 | class E( 76 | class E(
| _______^ | _______^
75 | | A, 77 | | A,
76 | | A 78 | | A
77 | | ): 79 | | ):
| |_^ | |_^
78 | # error: [unused-ignore-comment] 80 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base] 81 | x: int # type: ignore[duplicate-base]
| |
info: The definition of class `E` will raise `TypeError` at runtime 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] 75 | # error: [duplicate-base]
74 | class E( 76 | class E(
75 | A, 77 | A,
| - Class `A` first included in bases list here | - Class `A` first included in bases list here
76 | A 78 | A
| ^ Class `A` later repeated here | ^ Class `A` later repeated here
77 | ): 79 | ):
78 | # error: [unused-ignore-comment] 80 | # error: [unused-ignore-comment]
| |
info: rule `duplicate-base` is enabled by default info: rule `duplicate-base` is enabled by default
@ -341,14 +347,14 @@ info: rule `duplicate-base` is enabled by default
``` ```
info[unused-ignore-comment] info[unused-ignore-comment]
--> src/mdtest_snippet.py:79:13 --> src/mdtest_snippet.py:81:13
| |
77 | ): 79 | ):
78 | # error: [unused-ignore-comment] 80 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base] 81 | x: int # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
80 | 82 |
81 | # fmt: on 83 | # fmt: on
| |
``` ```

View file

@ -13,120 +13,121 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
``` ```
1 | from __future__ import annotations 1 | from __future__ import annotations
2 | 2 | from ty_extensions import reveal_mro
3 | class A: 3 |
4 | def a(self): ... 4 | class A:
5 | aa: int = 1 5 | def a(self): ...
6 | 6 | aa: int = 1
7 | class B(A): 7 |
8 | def b(self): ... 8 | class B(A):
9 | bb: int = 2 9 | def b(self): ...
10 | 10 | bb: int = 2
11 | class C(B): 11 |
12 | def c(self): ... 12 | class C(B):
13 | cc: int = 3 13 | def c(self): ...
14 | 14 | cc: int = 3
15 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>] 15 |
16 | 16 | reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
17 | super(C, C()).a 17 |
18 | super(C, C()).b 18 | super(C, C()).a
19 | super(C, C()).c # error: [unresolved-attribute] 19 | super(C, C()).b
20 | 20 | super(C, C()).c # error: [unresolved-attribute]
21 | super(B, C()).a 21 |
22 | super(B, C()).b # error: [unresolved-attribute] 22 | super(B, C()).a
23 | super(B, C()).c # error: [unresolved-attribute] 23 | super(B, C()).b # error: [unresolved-attribute]
24 | 24 | super(B, C()).c # error: [unresolved-attribute]
25 | super(A, C()).a # error: [unresolved-attribute] 25 |
26 | super(A, C()).b # error: [unresolved-attribute] 26 | super(A, C()).a # error: [unresolved-attribute]
27 | super(A, C()).c # error: [unresolved-attribute] 27 | super(A, C()).b # error: [unresolved-attribute]
28 | 28 | super(A, C()).c # error: [unresolved-attribute]
29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown 29 |
30 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown 30 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
31 | reveal_type(super(C, C()).aa) # revealed: int 31 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
32 | reveal_type(super(C, C()).bb) # revealed: int 32 | reveal_type(super(C, C()).aa) # revealed: int
33 | import types 33 | reveal_type(super(C, C()).bb) # revealed: int
34 | from typing_extensions import Callable, TypeIs, Literal, TypedDict 34 | import types
35 | 35 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
36 | def f(): ... 36 |
37 | 37 | def f(): ...
38 | class Foo[T]: 38 |
39 | def method(self): ... 39 | class Foo[T]:
40 | @property 40 | def method(self): ...
41 | def some_property(self): ... 41 | @property
42 | 42 | def some_property(self): ...
43 | type Alias = int 43 |
44 | 44 | type Alias = int
45 | class SomeTypedDict(TypedDict): 45 |
46 | x: int 46 | class SomeTypedDict(TypedDict):
47 | y: bytes 47 | x: int
48 | 48 | y: bytes
49 | # revealed: <super: <class 'object'>, FunctionType> 49 |
50 | reveal_type(super(object, f)) 50 | # revealed: <super: <class 'object'>, FunctionType>
51 | # revealed: <super: <class 'object'>, WrapperDescriptorType> 51 | reveal_type(super(object, f))
52 | reveal_type(super(object, types.FunctionType.__get__)) 52 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
53 | # revealed: <super: <class 'object'>, GenericAlias> 53 | reveal_type(super(object, types.FunctionType.__get__))
54 | reveal_type(super(object, Foo[int])) 54 | # revealed: <super: <class 'object'>, GenericAlias>
55 | # revealed: <super: <class 'object'>, _SpecialForm> 55 | reveal_type(super(object, Foo[int]))
56 | reveal_type(super(object, Literal)) 56 | # revealed: <super: <class 'object'>, _SpecialForm>
57 | # revealed: <super: <class 'object'>, TypeAliasType> 57 | reveal_type(super(object, Literal))
58 | reveal_type(super(object, Alias)) 58 | # revealed: <super: <class 'object'>, TypeAliasType>
59 | # revealed: <super: <class 'object'>, MethodType> 59 | reveal_type(super(object, Alias))
60 | reveal_type(super(object, Foo().method)) 60 | # revealed: <super: <class 'object'>, MethodType>
61 | # revealed: <super: <class 'object'>, property> 61 | reveal_type(super(object, Foo().method))
62 | reveal_type(super(object, Foo.some_property)) 62 | # revealed: <super: <class 'object'>, property>
63 | 63 | reveal_type(super(object, Foo.some_property))
64 | def g(x: object) -> TypeIs[list[object]]: 64 |
65 | return isinstance(x, list) 65 | def g(x: object) -> TypeIs[list[object]]:
66 | 66 | return isinstance(x, list)
67 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]): 67 |
68 | if hasattr(x, "bar"): 68 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
69 | # revealed: <Protocol with members 'bar'> 69 | if hasattr(x, "bar"):
70 | reveal_type(x) 70 | # revealed: <Protocol with members 'bar'>
71 | # error: [invalid-super-argument] 71 | reveal_type(x)
72 | # revealed: Unknown 72 | # error: [invalid-super-argument]
73 | reveal_type(super(object, x)) 73 | # revealed: Unknown
74 | 74 | reveal_type(super(object, x))
75 | # error: [invalid-super-argument] 75 |
76 | # revealed: Unknown 76 | # error: [invalid-super-argument]
77 | reveal_type(super(object, z)) 77 | # revealed: Unknown
78 | 78 | reveal_type(super(object, z))
79 | is_list = g(x) 79 |
80 | # revealed: TypeIs[list[object] @ x] 80 | is_list = g(x)
81 | reveal_type(is_list) 81 | # revealed: TypeIs[list[object] @ x]
82 | # revealed: <super: <class 'object'>, bool> 82 | reveal_type(is_list)
83 | reveal_type(super(object, is_list)) 83 | # revealed: <super: <class 'object'>, bool>
84 | 84 | reveal_type(super(object, is_list))
85 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]> 85 |
86 | reveal_type(super(object, y)) 86 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
87 | 87 | reveal_type(super(object, y))
88 | # The first argument to `super()` must be an actual class object; 88 |
89 | # instances of `GenericAlias` are not accepted at runtime: 89 | # The first argument to `super()` must be an actual class object;
90 | # 90 | # instances of `GenericAlias` are not accepted at runtime:
91 | # error: [invalid-super-argument] 91 | #
92 | # revealed: Unknown 92 | # error: [invalid-super-argument]
93 | reveal_type(super(list[int], [])) 93 | # revealed: Unknown
94 | class Super: 94 | reveal_type(super(list[int], []))
95 | def method(self) -> int: 95 | class Super:
96 | return 42 96 | def method(self) -> int:
97 | 97 | return 42
98 | class Sub(Super): 98 |
99 | def method(self: Sub) -> int: 99 | class Sub(Super):
100 | # revealed: <super: <class 'Sub'>, Sub> 100 | def method(self: Sub) -> int:
101 | return reveal_type(super(self.__class__, self)).method() 101 | # revealed: <super: <class 'Sub'>, Sub>
102 | return reveal_type(super(self.__class__, self)).method()
``` ```
# Diagnostics # Diagnostics
``` ```
error[unresolved-attribute]: Object of type `<super: <class 'C'>, C>` has no attribute `c` 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()).a
18 | super(C, C()).b 19 | super(C, C()).b
19 | super(C, C()).c # error: [unresolved-attribute] 20 | super(C, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
20 | 21 |
21 | super(B, C()).a 22 | super(B, C()).a
| |
info: rule `unresolved-attribute` is enabled by default 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` 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()).a
22 | super(B, C()).b # error: [unresolved-attribute] 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 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` 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()).a
22 | super(B, C()).b # error: [unresolved-attribute] 23 | super(B, C()).b # error: [unresolved-attribute]
23 | super(B, C()).c # error: [unresolved-attribute] 24 | super(B, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
24 | 25 |
25 | super(A, C()).a # error: [unresolved-attribute] 26 | super(A, C()).a # error: [unresolved-attribute]
| |
info: rule `unresolved-attribute` is enabled by default 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` 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 | super(B, C()).c # error: [unresolved-attribute]
24 | 25 |
25 | super(A, C()).a # error: [unresolved-attribute] 26 | super(A, C()).a # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
26 | super(A, C()).b # 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 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` 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()).a # error: [unresolved-attribute]
26 | super(A, C()).b # 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 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` 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()).a # error: [unresolved-attribute]
26 | super(A, C()).b # 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]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
28 | 29 |
29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown 30 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
| |
info: rule `unresolved-attribute` is enabled by default 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 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 | # error: [invalid-super-argument]
72 | # revealed: Unknown 73 | # revealed: Unknown
73 | reveal_type(super(object, x)) 74 | reveal_type(super(object, x))
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
74 | 75 |
75 | # error: [invalid-super-argument] 76 | # error: [invalid-super-argument]
| |
info: rule `invalid-super-argument` is enabled by default 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 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 | # error: [invalid-super-argument]
76 | # revealed: Unknown 77 | # revealed: Unknown
77 | reveal_type(super(object, z)) 78 | reveal_type(super(object, z))
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
78 | 79 |
79 | is_list = g(x) 80 | is_list = g(x)
| |
info: rule `invalid-super-argument` is enabled by default 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 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 | # error: [invalid-super-argument]
92 | # revealed: Unknown 93 | # revealed: Unknown
93 | reveal_type(super(list[int], [])) 94 | reveal_type(super(list[int], []))
| ^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^
94 | class Super: 95 | class Super:
95 | def method(self) -> int: 96 | def method(self) -> int:
| |
info: rule `invalid-super-argument` is enabled by default info: rule `invalid-super-argument` is enabled by default

View file

@ -11,12 +11,14 @@ In type stubs, classes can reference themselves in their base class definitions.
`typeshed`, we have `class str(Sequence[str]): ...`. `typeshed`, we have `class str(Sequence[str]): ...`.
```pyi ```pyi
from ty_extensions import reveal_mro
class Foo[T]: ... class Foo[T]: ...
class Bar(Foo[Bar]): ... class Bar(Foo[Bar]): ...
reveal_type(Bar) # revealed: <class '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 ## Access to attributes declared in stubs

View file

@ -125,13 +125,14 @@ The stdlib API `os.stat` is a commonly used API that returns an instance of a tu
```py ```py
import os import os
import stat 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")) # revealed: stat_result
reveal_type(os.stat("my_file.txt")[stat.ST_MODE]) # revealed: int 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 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'>] # 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_type(os.stat_result.__mro__) reveal_mro(os.stat_result)
# There are no specific overloads for the `float` elements in `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 # because the fallback `(self, index: SupportsIndex, /) -> int | float` overload
@ -336,15 +337,17 @@ python-version = "3.9"
``` ```
```py ```py
from ty_extensions import reveal_mro
class A(tuple[int, str]): ... 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'>] # 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_type(A.__mro__) reveal_mro(A)
class C(tuple): ... 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'>] # 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_type(C.__mro__) reveal_mro(C)
``` ```
## `typing.Tuple` ## `typing.Tuple`
@ -376,16 +379,17 @@ python-version = "3.9"
```py ```py
from typing import Tuple from typing import Tuple
from ty_extensions import reveal_mro
class A(Tuple[int, str]): ... 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'>] # 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_type(A.__mro__) reveal_mro(A)
class C(Tuple): ... 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'>] # 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_type(C.__mro__) reveal_mro(C)
``` ```
### Union subscript access ### Union subscript access

View file

@ -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. annotation), as opposed to `Any` which represents an explicitly unknown type.
```py ```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(Unknown, int))
static_assert(is_assignable_to(int, Unknown)) 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 ```py
class C(Unknown): ... class C(Unknown): ...
# revealed: tuple[<class 'C'>, Unknown, <class 'object'>] # revealed: (<class 'C'>, Unknown, <class 'object'>)
reveal_type(C.__mro__) reveal_mro(C)
# error: "Special form `ty_extensions.Unknown` expected no type parameter" # error: "Special form `ty_extensions.Unknown` expected no type parameter"
u: Unknown[str] u: Unknown[str]

View file

@ -145,10 +145,12 @@ _: type[A, B]
## As a base class ## As a base class
```py ```py
from ty_extensions import reveal_mro
class Foo(type[int]): ... class Foo(type[int]): ...
# TODO: should be `tuple[<class 'Foo'>, <class 'type'>, <class 'object'>] # 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 ## Display of generic `type[]` types

View file

@ -23,10 +23,11 @@ not a class.
```py ```py
from typing import Type from typing import Type
from ty_extensions import reveal_mro
class C(Type): ... class C(Type): ...
# Runtime value: `(C, type, typing.Generic, object)` # Runtime value: `(C, type, typing.Generic, object)`
# TODO: Add `Generic` to the MRO # 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'>)
``` ```

View file

@ -123,6 +123,7 @@ python-version = "3.12"
```py ```py
from typing import ClassVar from typing import ClassVar
from ty_extensions import reveal_mro
# error: [invalid-type-form] "`ClassVar` annotations are only allowed in class-body scopes" # error: [invalid-type-form] "`ClassVar` annotations are only allowed in class-body scopes"
x: ClassVar[int] = 1 x: ClassVar[int] = 1
@ -155,8 +156,8 @@ def f[T](x: T) -> ClassVar[T]:
class Foo(ClassVar[tuple[int]]): ... 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 # 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'>] # revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
reveal_type(Foo.__mro__) reveal_mro(Foo)
``` ```
[`typing.classvar`]: https://docs.python.org/3/library/typing.html#typing.ClassVar [`typing.classvar`]: https://docs.python.org/3/library/typing.html#typing.ClassVar

View file

@ -265,6 +265,7 @@ python-version = "3.12"
```py ```py
from typing import Final, ClassVar, Annotated from typing import Final, ClassVar, Annotated
from ty_extensions import reveal_mro
LEGAL_A: Final[int] = 1 LEGAL_A: Final[int] = 1
LEGAL_B: Final = 1 LEGAL_B: Final = 1
@ -304,8 +305,8 @@ def f[T](x: T) -> Final[T]:
class Foo(Final[tuple[int]]): ... 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 # 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'>] # revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
reveal_type(Foo.__mro__) reveal_mro(Foo)
``` ```
### Attribute assignment outside `__init__` ### Attribute assignment outside `__init__`

View file

@ -4364,14 +4364,6 @@ impl<'db> Type<'db> {
} }
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => { 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 { if let Some(enum_class) = match self {
Type::ClassLiteral(literal) => Some(literal), Type::ClassLiteral(literal) => Some(literal),
Type::SubclassOf(subclass_of) => subclass_of 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( let class_attr_fallback = Self::try_call_dunder_get_on_attribute(
db, db,
class_attr_plain, class_attr_plain,

View file

@ -1981,11 +1981,6 @@ impl<'db> ClassLiteral<'db> {
name: &str, name: &str,
policy: MemberLookupPolicy, policy: MemberLookupPolicy,
) -> PlaceAndQualifiers<'db> { ) -> 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)) self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))
} }

View file

@ -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> { impl<'db> From<ClassType<'db>> for ClassBase<'db> {

View file

@ -1319,6 +1319,8 @@ pub enum KnownFunction {
HasMember, HasMember,
/// `ty_extensions.reveal_protocol_interface` /// `ty_extensions.reveal_protocol_interface`
RevealProtocolInterface, RevealProtocolInterface,
/// `ty_extensions.reveal_mro`
RevealMro,
/// `ty_extensions.range_constraint` /// `ty_extensions.range_constraint`
RangeConstraint, RangeConstraint,
/// `ty_extensions.negated_range_constraint` /// `ty_extensions.negated_range_constraint`
@ -1397,6 +1399,7 @@ impl KnownFunction {
| Self::RevealProtocolInterface | Self::RevealProtocolInterface
| Self::RangeConstraint | Self::RangeConstraint
| Self::NegatedRangeConstraint | Self::NegatedRangeConstraint
| Self::RevealMro
| Self::AllMembers => module.is_ty_extensions(), | Self::AllMembers => module.is_ty_extensions(),
Self::ImportModule => module.is_importlib(), 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 => { KnownFunction::IsInstance | KnownFunction::IsSubclass => {
let [Some(first_arg), Some(second_argument)] = parameter_types else { let [Some(first_arg), Some(second_argument)] = parameter_types else {
return; return;
@ -1822,6 +1904,7 @@ pub(crate) mod tests {
| KnownFunction::RevealProtocolInterface | KnownFunction::RevealProtocolInterface
| KnownFunction::RangeConstraint | KnownFunction::RangeConstraint
| KnownFunction::NegatedRangeConstraint | KnownFunction::NegatedRangeConstraint
| KnownFunction::RevealMro
| KnownFunction::AllMembers => KnownModule::TyExtensions, | KnownFunction::AllMembers => KnownModule::TyExtensions,
KnownFunction::ImportModule => KnownModule::ImportLib, KnownFunction::ImportModule => KnownModule::ImportLib,

View file

@ -345,32 +345,24 @@ impl Matcher {
return false; return false;
}; };
// reveal_type // reveal_type, reveal_protocol_interface
if primary_message == "Revealed type" if matches!(
&& primary_annotation == expected_reveal_type_message primary_message,
"Revealed type" | "Revealed protocol interface"
) && primary_annotation == expected_reveal_type_message
{ {
return true; return true;
} }
// reveal_protocol_interface // reveal_when_assignable_to, reveal_when_subtype_of, reveal_mro
if primary_message == "Revealed protocol interface" if matches!(
&& primary_annotation == expected_reveal_type_message primary_message,
"Assignability holds" | "Subtyping holds" | "Revealed MRO"
) && primary_annotation == expected_type
{ {
return true; 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 false
}; };

View file

@ -1,5 +1,6 @@
# ruff: noqa: PYI021 # ruff: noqa: PYI021
import sys import sys
import types
from collections.abc import Iterable from collections.abc import Iterable
from enum import Enum from enum import Enum
from typing import ( 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. # Returns `True` if the given object has a member with the given name.
def has_member(obj: Any, name: str) -> bool: ... 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 Passing a non-protocol type will cause ty to emit an error diagnostic.
# 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: ... 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 # A protocol describing an interface that should be satisfied by all named tuples
# created using `typing.NamedTuple` or `collections.namedtuple`. # created using `typing.NamedTuple` or `collections.namedtuple`.