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