[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-simple-long-identifier,main.py,0,1
tstring-completions,main.py,0,1
ty-extensions-lower-stdlib,main.py,0,7
ty-extensions-lower-stdlib,main.py,0,8
type-var-typing-over-ast,main.py,0,3
type-var-typing-over-ast,main.py,1,270

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

View file

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

View file

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

View file

@ -114,6 +114,7 @@ The aliases can be inherited from. Some of these are still partially or wholly T
```py
import typing
from ty_extensions import reveal_mro
####################
### Built-ins
@ -121,23 +122,23 @@ import typing
class ListSubclass(typing.List): ...
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(ListSubclass.__mro__)
# revealed: (<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(ListSubclass)
class DictSubclass(typing.Dict): ...
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(DictSubclass.__mro__)
# revealed: (<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DictSubclass)
class SetSubclass(typing.Set): ...
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(SetSubclass.__mro__)
# revealed: (<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(SetSubclass)
class FrozenSetSubclass(typing.FrozenSet): ...
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(FrozenSetSubclass.__mro__)
# revealed: (<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(FrozenSetSubclass)
####################
### `collections`
@ -145,26 +146,26 @@ reveal_type(FrozenSetSubclass.__mro__)
class ChainMapSubclass(typing.ChainMap): ...
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(ChainMapSubclass.__mro__)
# revealed: (<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(ChainMapSubclass)
class CounterSubclass(typing.Counter): ...
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(CounterSubclass.__mro__)
# revealed: (<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(CounterSubclass)
class DefaultDictSubclass(typing.DefaultDict): ...
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(DefaultDictSubclass.__mro__)
# revealed: (<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DefaultDictSubclass)
class DequeSubclass(typing.Deque): ...
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(DequeSubclass.__mro__)
# revealed: (<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DequeSubclass)
class OrderedDictSubclass(typing.OrderedDict): ...
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(OrderedDictSubclass.__mro__)
# revealed: (<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(OrderedDictSubclass)
```

View file

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

View file

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

View file

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

View file

@ -13,6 +13,8 @@ python-version = "3.12"
```
```py
from ty_extensions import reveal_mro
A = int
class G[T]: ...
@ -21,5 +23,5 @@ class C(A, G["B"]): ...
A = str
B = bytes
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>)
```

View file

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

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

View file

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

View file

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

View file

@ -1,55 +1,74 @@
# Method Resolution Order tests
Tests that assert that we can infer the correct type for a class's `__mro__` attribute.
Tests that assert that we can infer the correct MRO for a class.
This attribute is rarely accessed directly at runtime. However, it's extremely important for *us* to
know the precise possible values of a class's Method Resolution Order, or we won't be able to infer
the correct type of attributes accessed from instances.
It's extremely important for us to know the precise possible values of a class's Method Resolution
Order, or we won't be able to infer the correct type of attributes accessed from instances.
For documentation on method resolution orders, see:
- <https://docs.python.org/3/glossary.html#term-method-resolution-order>
- <https://docs.python.org/3/howto/mro.html#python-2-3-mro>
At runtime, the MRO for a class can be inspected using the `__mro__` attribute. However, rather than
special-casing inference of that attribute, we allow our inferred MRO of a class to be introspected
using the `ty_extensions.reveal_mro` function. This is because the MRO ty infers for a class will
often be different than a class's "real MRO" at runtime. This is often deliberate and desirable, but
would be confusing to users. For example, typeshed pretends that builtin sequences such as `tuple`
and `list` inherit from `collections.abc.Sequence`, resulting in a much longer inferred MRO for
these classes than what they actually have at runtime. Other differences to "real MROs" at runtime
include the facts that ty's inferred MRO will often include non-class elements, such as generic
aliases, `Any` and `Unknown`.
## No bases
```py
from ty_extensions import reveal_mro
class C: ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, <class 'object'>)
```
## The special case: `object` itself
```py
reveal_type(object.__mro__) # revealed: tuple[<class 'object'>]
from ty_extensions import reveal_mro
reveal_mro(object) # revealed: (<class 'object'>,)
```
## Explicit inheritance from `object`
```py
from ty_extensions import reveal_mro
class C(object): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, <class 'object'>)
```
## Explicit inheritance from non-`object` single base
```py
from ty_extensions import reveal_mro
class A: ...
class B(A): ...
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
reveal_mro(B) # revealed: (<class 'B'>, <class 'A'>, <class 'object'>)
```
## Linearization of multiple bases
```py
from ty_extensions import reveal_mro
class A: ...
class B: ...
class C(A, B): ...
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
```
## Complex diamond inheritance (1)
@ -57,14 +76,16 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>,
This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
```py
from ty_extensions import reveal_mro
class O: ...
class X(O): ...
class Y(O): ...
class A(X, Y): ...
class B(Y, X): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
reveal_mro(B) # revealed: (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
```
## Complex diamond inheritance (2)
@ -72,6 +93,8 @@ reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>,
This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
```py
from ty_extensions import reveal_mro
class O: ...
class F(O): ...
class E(O): ...
@ -80,12 +103,12 @@ class C(D, F): ...
class B(D, E): ...
class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
reveal_type(C.__mro__)
# revealed: tuple[<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>]
reveal_type(A.__mro__)
# revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_mro(C)
# revealed: (<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>)
reveal_mro(B)
# revealed: (<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_mro(A)
```
## Complex diamond inheritance (3)
@ -93,6 +116,8 @@ reveal_type(A.__mro__)
This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
```py
from ty_extensions import reveal_mro
class O: ...
class F(O): ...
class E(O): ...
@ -101,12 +126,12 @@ class C(D, F): ...
class B(E, D): ...
class A(B, C): ...
# revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
reveal_type(C.__mro__)
# revealed: tuple[<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__)
# revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
reveal_type(A.__mro__)
# revealed: (<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_mro(C)
# revealed: (<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>)
reveal_mro(B)
# revealed: (<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>)
reveal_mro(A)
```
## Complex diamond inheritance (4)
@ -114,6 +139,8 @@ reveal_type(A.__mro__)
This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
```py
from ty_extensions import reveal_mro
class O: ...
class A(O): ...
class B(O): ...
@ -125,19 +152,20 @@ class K2(D, B, E): ...
class K3(D, A): ...
class Z(K1, K2, K3): ...
# revealed: tuple[<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>]
reveal_type(K1.__mro__)
# revealed: tuple[<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>]
reveal_type(K2.__mro__)
# revealed: tuple[<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>]
reveal_type(K3.__mro__)
# revealed: tuple[<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>]
reveal_type(Z.__mro__)
# revealed: (<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>)
reveal_mro(K1)
# revealed: (<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>)
reveal_mro(K2)
# revealed: (<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>)
reveal_mro(K3)
# revealed: (<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>)
reveal_mro(Z)
```
## Inheritance from `Unknown`
```py
from ty_extensions import reveal_mro
from does_not_exist import DoesNotExist # error: [unresolved-import]
class A(DoesNotExist): ...
@ -147,11 +175,11 @@ class D(A, B, C): ...
class E(B, C): ...
class F(E, A): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, Unknown, <class 'object'>]
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>]
reveal_type(E.__mro__) # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>]
# revealed: tuple[<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>]
reveal_type(F.__mro__)
reveal_mro(A) # revealed: (<class 'A'>, Unknown, <class 'object'>)
reveal_mro(D) # revealed: (<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>)
reveal_mro(E) # revealed: (<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>)
# revealed: (<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>)
reveal_mro(F)
```
## Inheritance with intersections that include `Unknown`
@ -160,23 +188,22 @@ An intersection that includes `Unknown` or `Any` is permitted as long as the int
disjoint from `type`.
```py
from ty_extensions import reveal_mro
from does_not_exist import DoesNotExist # error: [unresolved-import]
reveal_type(DoesNotExist) # revealed: Unknown
if hasattr(DoesNotExist, "__mro__"):
# TODO: this should be `Unknown & <Protocol with members '__mro__'>` or similar
# (The second part of the intersection is incorrectly simplified to `object` due to https://github.com/astral-sh/ty/issues/986)
reveal_type(DoesNotExist) # revealed: Unknown
reveal_type(DoesNotExist) # revealed: Unknown & <Protocol with members '__mro__'>
class Foo(DoesNotExist): ... # no error!
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
if not isinstance(DoesNotExist, type):
reveal_type(DoesNotExist) # revealed: Unknown & ~type
class Foo(DoesNotExist): ... # error: [unsupported-base]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
```
## Inheritance from `type[Any]` and `type[Unknown]`
@ -186,14 +213,14 @@ guarantee:
```py
from typing import Any
from ty_extensions import Unknown, Intersection
from ty_extensions import Unknown, Intersection, reveal_mro
def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
class Foo(x): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Any, <class 'object'>)
class Bar(y): ...
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
```
## `__bases__` lists that cause errors at runtime
@ -202,14 +229,16 @@ If the class's `__bases__` cause an exception to be raised at runtime and theref
creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
```py
from ty_extensions import reveal_mro
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
class Foo(object, int): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
class Bar(Foo): ...
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Bar) # revealed: (<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>)
# This is the `TypeError` at the bottom of "ex_2"
# in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
@ -219,17 +248,17 @@ class Y(O): ...
class A(X, Y): ...
class B(Y, X): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__) # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
reveal_mro(B) # revealed: (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
class Z(A, B): ...
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
class AA(Z): ...
reveal_type(AA.__mro__) # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>]
reveal_mro(AA) # revealed: (<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>)
```
## `__bases__` includes a `Union`
@ -241,6 +270,8 @@ find a union type in a class's bases, we infer the class's `__mro__` as being
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
```py
from ty_extensions import reveal_mro
def returns_bool() -> bool:
return True
@ -257,7 +288,7 @@ reveal_type(x) # revealed: <class 'A'> | <class 'B'>
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
class Foo(x): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
```
## `__bases__` is a union of a dynamic type and valid bases
@ -268,6 +299,7 @@ diagnostic, and we use the dynamic type as a base to prevent further downstream
```py
from typing import Any
from ty_extensions import reveal_mro
def _(flag: bool, any: Any):
if flag:
@ -276,12 +308,14 @@ def _(flag: bool, any: Any):
class Base: ...
class Foo(Base): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Any, <class 'object'>)
```
## `__bases__` includes multiple `Union`s
```py
from ty_extensions import reveal_mro
def returns_bool() -> bool:
return True
@ -307,12 +341,14 @@ reveal_type(y) # revealed: <class 'C'> | <class 'D'>
# error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
class Foo(x, y): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
```
## `__bases__` lists that cause errors... now with `Union`s
```py
from ty_extensions import reveal_mro
def returns_bool() -> bool:
return True
@ -328,11 +364,11 @@ else:
# error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
class PossibleError(foo, X): ...
reveal_type(PossibleError.__mro__) # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
reveal_mro(PossibleError) # revealed: (<class 'PossibleError'>, Unknown, <class 'object'>)
class A(X, Y): ...
reveal_type(A.__mro__) # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
reveal_mro(A) # revealed: (<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>)
if returns_bool():
class B(X, Y): ...
@ -340,13 +376,13 @@ if returns_bool():
else:
class B(Y, X): ...
# revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
reveal_type(B.__mro__)
# revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
reveal_mro(B)
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
class Z(A, B): ...
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
```
## `__bases__` lists that include objects that are not instances of `type`
@ -389,9 +425,11 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
<!-- snapshot-diagnostics -->
```py
from ty_extensions import reveal_mro
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
class Spam: ...
class Eggs: ...
@ -413,12 +451,12 @@ class Ham(
# fmt: on
reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
reveal_mro(Ham) # revealed: (<class 'Ham'>, Unknown, <class 'object'>)
class Mushrooms: ...
class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
reveal_mro(Omelette) # revealed: (<class 'Omelette'>, Unknown, <class 'object'>)
# fmt: off
@ -494,6 +532,7 @@ however, for gradual types this would break the
the dynamic base can usually be materialised to a type that would lead to a resolvable MRO.
```py
from ty_extensions import reveal_mro
from unresolvable_module import UnknownBase1, UnknownBase2 # error: [unresolved-import]
reveal_type(UnknownBase1) # revealed: Unknown
@ -502,7 +541,7 @@ reveal_type(UnknownBase2) # revealed: Unknown
# no error here -- we respect the gradual guarantee:
class Foo(UnknownBase1, UnknownBase2): ...
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
```
However, if there are duplicate class elements, we do emit an error, even if there are also multiple
@ -513,7 +552,7 @@ bases materialize to:
# error: [duplicate-base] "Duplicate base class `Foo`"
class Bar(UnknownBase1, Foo, UnknownBase2, Foo): ...
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
```
## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
@ -529,25 +568,26 @@ reveal_type(unknown_object.__mro__) # revealed: Unknown
```py
from typing import Generic, TypeVar, Iterator
from ty_extensions import reveal_mro
T = TypeVar("T")
class peekable(Generic[T], Iterator[T]): ...
# revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(peekable.__mro__)
# revealed: (<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(peekable)
class peekable2(Iterator[T], Generic[T]): ...
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(peekable2.__mro__)
# revealed: (<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(peekable2)
class Base: ...
class Intermediate(Base, Generic[T]): ...
class Sub(Intermediate[T], Base): ...
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>]
reveal_type(Sub.__mro__)
# revealed: (<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>)
reveal_mro(Sub)
```
## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
@ -569,17 +609,19 @@ class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
These are invalid, but we need to be able to handle them gracefully without panicking.
```pyi
from ty_extensions import reveal_mro
class Foo(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo) # revealed: <class 'Foo'>
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
class Bar: ...
class Baz: ...
class Boz(Bar, Baz, Boz): ... # error: [cyclic-class-definition]
reveal_type(Boz) # revealed: <class 'Boz'>
reveal_type(Boz.__mro__) # revealed: tuple[<class 'Boz'>, Unknown, <class 'object'>]
reveal_mro(Boz) # revealed: (<class 'Boz'>, Unknown, <class 'object'>)
```
## Classes with indirect cycles in their MROs
@ -587,31 +629,37 @@ reveal_type(Boz.__mro__) # revealed: tuple[<class 'Boz'>, Unknown, <class 'obje
These are similarly unlikely, but we still shouldn't crash:
```pyi
from ty_extensions import reveal_mro
class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
```
## Classes with cycles in their MROs, and multiple inheritance
```pyi
from ty_extensions import reveal_mro
class Spam: ...
class Foo(Bar): ... # error: [cyclic-class-definition]
class Bar(Baz): ... # error: [cyclic-class-definition]
class Baz(Foo, Spam): ... # error: [cyclic-class-definition]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
reveal_mro(Bar) # revealed: (<class 'Bar'>, Unknown, <class 'object'>)
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
```
## Classes with cycles in their MRO, and a sub-graph
```pyi
from ty_extensions import reveal_mro
class FooCycle(BarCycle): ... # error: [cyclic-class-definition]
class Foo: ...
class BarCycle(FooCycle): ... # error: [cyclic-class-definition]
@ -622,10 +670,10 @@ class Bar(Foo): ...
class Baz(Bar, BarCycle): ...
class Spam(Baz): ...
reveal_type(FooCycle.__mro__) # revealed: tuple[<class 'FooCycle'>, Unknown, <class 'object'>]
reveal_type(BarCycle.__mro__) # revealed: tuple[<class 'BarCycle'>, Unknown, <class 'object'>]
reveal_type(Baz.__mro__) # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
reveal_type(Spam.__mro__) # revealed: tuple[<class 'Spam'>, Unknown, <class 'object'>]
reveal_mro(FooCycle) # revealed: (<class 'FooCycle'>, Unknown, <class 'object'>)
reveal_mro(BarCycle) # revealed: (<class 'BarCycle'>, Unknown, <class 'object'>)
reveal_mro(Baz) # revealed: (<class 'Baz'>, Unknown, <class 'object'>)
reveal_mro(Spam) # revealed: (<class 'Spam'>, Unknown, <class 'object'>)
```
## Other classes with possible cycles
@ -636,20 +684,22 @@ python-version = "3.13"
```
```pyi
from ty_extensions import reveal_mro
class C(C.a): ...
reveal_type(C.__class__) # revealed: <class 'type'>
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
class D(D.a):
a: D
reveal_type(D.__class__) # revealed: <class 'type'>
reveal_type(D.__mro__) # revealed: tuple[<class 'D'>, Unknown, <class 'object'>]
reveal_mro(D) # revealed: (<class 'D'>, Unknown, <class 'object'>)
class E[T](E.a): ...
reveal_type(E.__class__) # revealed: <class 'type'>
reveal_type(E.__mro__) # revealed: tuple[<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>]
reveal_mro(E) # revealed: (<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>)
class F[T](F(), F): ... # error: [cyclic-class-definition]
reveal_type(F.__class__) # revealed: type[Unknown]
reveal_type(F.__mro__) # revealed: tuple[<class 'F[Unknown]'>, Unknown, <class 'object'>]
reveal_mro(F) # revealed: (<class 'F[Unknown]'>, Unknown, <class 'object'>)
```

View file

@ -9,7 +9,7 @@ name, and not just by its numeric position within the tuple:
```py
from typing import NamedTuple
from ty_extensions import static_assert, is_subtype_of, is_assignable_to
from ty_extensions import static_assert, is_subtype_of, is_assignable_to, reveal_mro
class Person(NamedTuple):
id: int
@ -25,8 +25,8 @@ reveal_type(alice.id) # revealed: int
reveal_type(alice.name) # revealed: str
reveal_type(alice.age) # revealed: int | None
# revealed: tuple[<class 'Person'>, <class 'tuple[int, str, int | None]'>, <class 'Sequence[int | str | None]'>, <class 'Reversible[int | str | None]'>, <class 'Collection[int | str | None]'>, <class 'Iterable[int | str | None]'>, <class 'Container[int | str | None]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(Person.__mro__)
# revealed: (<class 'Person'>, <class 'tuple[int, str, int | None]'>, <class 'Sequence[int | str | None]'>, <class 'Reversible[int | str | None]'>, <class 'Collection[int | str | None]'>, <class 'Iterable[int | str | None]'>, <class 'Container[int | str | None]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Person)
static_assert(is_subtype_of(Person, tuple[int, str, int | None]))
static_assert(is_subtype_of(Person, tuple[object, ...]))
@ -329,9 +329,8 @@ reveal_type(typing.NamedTuple.__name__) # revealed: str
reveal_type(typing.NamedTuple.__qualname__) # revealed: str
reveal_type(typing.NamedTuple.__kwdefaults__) # revealed: dict[str, Any] | None
# TODO: this should cause us to emit a diagnostic and reveal `Unknown` (function objects don't have an `__mro__` attribute),
# but the fact that we don't isn't actually a `NamedTuple` bug (https://github.com/astral-sh/ty/issues/986)
reveal_type(typing.NamedTuple.__mro__) # revealed: tuple[<class 'FunctionType'>, <class 'object'>]
# error: [unresolved-attribute]
reveal_type(typing.NamedTuple.__mro__) # revealed: Unknown
```
By the normal rules, `NamedTuple` and `type[NamedTuple]` should not be valid in type expressions --

View file

@ -25,10 +25,11 @@ A protocol is defined by inheriting from the `Protocol` class, which is annotate
```py
from typing import Protocol
from ty_extensions import reveal_mro
class MyProtocol(Protocol): ...
reveal_type(MyProtocol.__mro__) # revealed: tuple[<class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_mro(MyProtocol) # revealed: (<class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
```
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
@ -37,7 +38,7 @@ class's bases:
```py
class Foo(Protocol, Protocol): ... # error: [duplicate-base]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
```
Protocols can also be generic, either by including `Generic[]` in the bases list, subscripting
@ -64,7 +65,7 @@ class Bar3[T](Protocol[T]):
# Note that this class definition *will* actually succeed at runtime,
# unlike classes that combine PEP-695 type parameters with inheritance from `Generic[]`
reveal_type(Bar3.__mro__) # revealed: tuple[<class 'Bar3[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_mro(Bar3) # revealed: (<class 'Bar3[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
```
It's an error to include both bare `Protocol` and subscripted `Protocol[]` in the bases list
@ -74,8 +75,8 @@ simultaneously:
class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
x: T
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>]
reveal_type(DuplicateBases.__mro__)
# revealed: (<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>)
reveal_mro(DuplicateBases)
```
The introspection helper `typing(_extensions).is_protocol` can be used to verify whether a class is
@ -124,8 +125,8 @@ it is not sufficient for it to have `Protocol` in its MRO.
```py
class SubclassOfMyProtocol(MyProtocol): ...
# revealed: tuple[<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(SubclassOfMyProtocol.__mro__)
# revealed: (<class 'SubclassOfMyProtocol'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(SubclassOfMyProtocol)
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False]
```
@ -143,8 +144,8 @@ class OtherProtocol(Protocol):
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
# revealed: tuple[<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(ComplexInheritance.__mro__)
# revealed: (<class 'ComplexInheritance'>, <class 'SubProtocol'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(ComplexInheritance)
reveal_type(is_protocol(ComplexInheritance)) # revealed: Literal[True]
```
@ -156,22 +157,22 @@ or `TypeError` is raised at runtime when the class is created.
# error: [invalid-protocol] "Protocol class `Invalid` cannot inherit from non-protocol class `NotAProtocol`"
class Invalid(NotAProtocol, Protocol): ...
# revealed: tuple[<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(Invalid.__mro__)
# revealed: (<class 'Invalid'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Invalid)
# error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`"
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
# revealed: tuple[<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(AlsoInvalid.__mro__)
# revealed: (<class 'AlsoInvalid'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, <class 'NotAProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(AlsoInvalid)
class NotAGenericProtocol[T]: ...
# error: [invalid-protocol] "Protocol class `StillInvalid` cannot inherit from non-protocol class `NotAGenericProtocol`"
class StillInvalid(NotAGenericProtocol[int], Protocol): ...
# revealed: tuple[<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(StillInvalid.__mro__)
# revealed: (<class 'StillInvalid'>, <class 'NotAGenericProtocol[int]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(StillInvalid)
```
But two exceptions to this rule are `object` and `Generic`:
@ -188,7 +189,7 @@ T = TypeVar("T")
# type checkers.
class Fine(Protocol, object): ...
reveal_type(Fine.__mro__) # revealed: tuple[<class 'Fine'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_mro(Fine) # revealed: (<class 'Fine'>, typing.Protocol, typing.Generic, <class 'object'>)
class StillFine(Protocol, Generic[T], object): ...
class EvenThis[T](Protocol, object): ...
@ -202,8 +203,8 @@ And multiple inheritance from a mix of protocol and non-protocol classes is fine
```py
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
# revealed: tuple[<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>]
reveal_type(FineAndDandy.__mro__)
# revealed: (<class 'FineAndDandy'>, <class 'MyProtocol'>, <class 'OtherProtocol'>, typing.Protocol, typing.Generic, <class 'NotAProtocol'>, <class 'object'>)
reveal_mro(FineAndDandy)
```
But if `Protocol` is not present in the bases list, the resulting class doesn't count as a protocol
@ -270,11 +271,11 @@ second argument to `issubclass()` at runtime:
```py
import abc
import typing
from ty_extensions import TypeOf
from ty_extensions import TypeOf, reveal_mro
reveal_type(type(Protocol)) # revealed: <class '_ProtocolMeta'>
# revealed: tuple[<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>]
reveal_type(type(Protocol).__mro__)
# revealed: (<class '_ProtocolMeta'>, <class 'ABCMeta'>, <class 'type'>, <class 'object'>)
reveal_mro(type(Protocol))
static_assert(is_subtype_of(TypeOf[Protocol], type))
static_assert(is_subtype_of(TypeOf[Protocol], abc.ABCMeta))
static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta))

View file

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

View file

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

View file

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

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]): ...`.
```pyi
from ty_extensions import reveal_mro
class Foo[T]: ...
class Bar(Foo[Bar]): ...
reveal_type(Bar) # revealed: <class 'Bar'>
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>]
reveal_mro(Bar) # revealed: (<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>)
```
## Access to attributes declared in stubs

View file

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

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.
```py
from ty_extensions import Unknown, static_assert, is_assignable_to
from ty_extensions import Unknown, static_assert, is_assignable_to, reveal_mro
static_assert(is_assignable_to(Unknown, int))
static_assert(is_assignable_to(int, Unknown))
@ -107,8 +107,8 @@ def explicit_unknown(x: Unknown, y: tuple[str, Unknown], z: Unknown = 1) -> None
```py
class C(Unknown): ...
# revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
reveal_type(C.__mro__)
# revealed: (<class 'C'>, Unknown, <class 'object'>)
reveal_mro(C)
# error: "Special form `ty_extensions.Unknown` expected no type parameter"
u: Unknown[str]

View file

@ -145,10 +145,12 @@ _: type[A, B]
## As a base class
```py
from ty_extensions import reveal_mro
class Foo(type[int]): ...
# TODO: should be `tuple[<class 'Foo'>, <class 'type'>, <class 'object'>]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>]
reveal_mro(Foo) # revealed: (<class 'Foo'>, @Todo(GenericAlias instance), <class 'object'>)
```
## Display of generic `type[]` types

View file

@ -23,10 +23,11 @@ not a class.
```py
from typing import Type
from ty_extensions import reveal_mro
class C(Type): ...
# Runtime value: `(C, type, typing.Generic, object)`
# TODO: Add `Generic` to the MRO
reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'type'>, <class 'object'>]
reveal_mro(C) # revealed: (<class 'C'>, <class 'type'>, <class 'object'>)
```

View file

@ -123,6 +123,7 @@ python-version = "3.12"
```py
from typing import ClassVar
from ty_extensions import reveal_mro
# error: [invalid-type-form] "`ClassVar` annotations are only allowed in class-body scopes"
x: ClassVar[int] = 1
@ -155,8 +156,8 @@ def f[T](x: T) -> ClassVar[T]:
class Foo(ClassVar[tuple[int]]): ...
# TODO: Show `Unknown` instead of `@Todo` type in the MRO; or ignore `ClassVar` and show the MRO as if `ClassVar` was not there
# revealed: tuple[<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>]
reveal_type(Foo.__mro__)
# revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
reveal_mro(Foo)
```
[`typing.classvar`]: https://docs.python.org/3/library/typing.html#typing.ClassVar

View file

@ -265,6 +265,7 @@ python-version = "3.12"
```py
from typing import Final, ClassVar, Annotated
from ty_extensions import reveal_mro
LEGAL_A: Final[int] = 1
LEGAL_B: Final = 1
@ -304,8 +305,8 @@ def f[T](x: T) -> Final[T]:
class Foo(Final[tuple[int]]): ...
# TODO: Show `Unknown` instead of `@Todo` type in the MRO; or ignore `Final` and show the MRO as if `Final` was not there
# revealed: tuple[<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>]
reveal_type(Foo.__mro__)
# revealed: (<class 'Foo'>, @Todo(Inference of subscript on special form), <class 'object'>)
reveal_mro(Foo)
```
### Attribute assignment outside `__init__`

View file

@ -4364,14 +4364,6 @@ impl<'db> Type<'db> {
}
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
);
if name == "__mro__" {
return class_attr_plain;
}
if let Some(enum_class) = match self {
Type::ClassLiteral(literal) => Some(literal),
Type::SubclassOf(subclass_of) => subclass_of
@ -4392,6 +4384,10 @@ impl<'db> Type<'db> {
}
}
let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str,policy).expect(
"Calling `find_name_in_mro` on class literals and subclass-of types should always return `Some`",
);
let class_attr_fallback = Self::try_call_dunder_get_on_attribute(
db,
class_attr_plain,

View file

@ -1981,11 +1981,6 @@ impl<'db> ClassLiteral<'db> {
name: &str,
policy: MemberLookupPolicy,
) -> PlaceAndQualifiers<'db> {
if name == "__mro__" {
let tuple_elements = self.iter_mro(db, specialization);
return Place::bound(Type::heterogeneous_tuple(db, tuple_elements)).into();
}
self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))
}

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> {

View file

@ -1319,6 +1319,8 @@ pub enum KnownFunction {
HasMember,
/// `ty_extensions.reveal_protocol_interface`
RevealProtocolInterface,
/// `ty_extensions.reveal_mro`
RevealMro,
/// `ty_extensions.range_constraint`
RangeConstraint,
/// `ty_extensions.negated_range_constraint`
@ -1397,6 +1399,7 @@ impl KnownFunction {
| Self::RevealProtocolInterface
| Self::RangeConstraint
| Self::NegatedRangeConstraint
| Self::RevealMro
| Self::AllMembers => module.is_ty_extensions(),
Self::ImportModule => module.is_importlib(),
@ -1619,6 +1622,85 @@ impl KnownFunction {
}
}
KnownFunction::RevealMro => {
let [Some(param_type)] = parameter_types else {
return;
};
let mut good_argument = true;
let classes = match param_type {
Type::ClassLiteral(class) => vec![ClassType::NonGeneric(*class)],
Type::GenericAlias(generic_alias) => vec![ClassType::Generic(*generic_alias)],
Type::Union(union) => {
let elements = union.elements(db);
let mut classes = Vec::with_capacity(elements.len());
for element in elements {
match element {
Type::ClassLiteral(class) => {
classes.push(ClassType::NonGeneric(*class));
}
Type::GenericAlias(generic_alias) => {
classes.push(ClassType::Generic(*generic_alias));
}
_ => {
good_argument = false;
break;
}
}
}
classes
}
_ => {
good_argument = false;
vec![]
}
};
if !good_argument {
let Some(builder) =
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
else {
return;
};
let mut diagnostic =
builder.into_diagnostic("Invalid argument to `reveal_mro`");
diagnostic.set_primary_message(format_args!(
"Can only pass a class object, generic alias or a union thereof"
));
return;
}
if let Some(builder) =
context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info)
{
let mut diag = builder.into_diagnostic("Revealed MRO");
let span = context.span(&call_expression.arguments.args[0]);
let mut message = String::new();
for (i, class) in classes.iter().enumerate() {
message.push('(');
for class in class.iter_mro(db) {
message.push_str(&class.display(db).to_string());
// Omit the comma for the last element (which is always `object`)
if class
.into_class()
.is_none_or(|base| !base.is_object(context.db()))
{
message.push_str(", ");
}
}
// If the last element was also the first element
// (i.e., it's a length-1 tuple -- which can only happen if we're revealing
// the MRO for `object` itself), add a trailing comma so that it's still a
// valid tuple display.
if class.is_object(db) {
message.push(',');
}
message.push(')');
if i < classes.len() - 1 {
message.push_str(" | ");
}
}
diag.annotate(Annotation::primary(span).message(message));
}
}
KnownFunction::IsInstance | KnownFunction::IsSubclass => {
let [Some(first_arg), Some(second_argument)] = parameter_types else {
return;
@ -1822,6 +1904,7 @@ pub(crate) mod tests {
| KnownFunction::RevealProtocolInterface
| KnownFunction::RangeConstraint
| KnownFunction::NegatedRangeConstraint
| KnownFunction::RevealMro
| KnownFunction::AllMembers => KnownModule::TyExtensions,
KnownFunction::ImportModule => KnownModule::ImportLib,

View file

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

View file

@ -1,5 +1,6 @@
# ruff: noqa: PYI021
import sys
import types
from collections.abc import Iterable
from enum import Enum
from typing import (
@ -115,11 +116,16 @@ def all_members(obj: Any) -> tuple[str, ...]: ...
# Returns `True` if the given object has a member with the given name.
def has_member(obj: Any, name: str) -> bool: ...
def reveal_protocol_interface(protocol: type) -> None:
"""
Passing a protocol type to this function will cause ty to emit an info-level
diagnostic describing the protocol's interface.
# Passing a protocol type to this function will cause ty to emit an info-level
# diagnostic describing the protocol's interface. Passing a non-protocol type
# will cause ty to emit an error diagnostic.
def reveal_protocol_interface(protocol: type) -> None: ...
Passing a non-protocol type will cause ty to emit an error diagnostic.
"""
def reveal_mro(cls: type | types.GenericAlias) -> None:
"""Reveal the MRO that ty infers for the given class or generic alias."""
# A protocol describing an interface that should be satisfied by all named tuples
# created using `typing.NamedTuple` or `collections.namedtuple`.