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