mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 20:08:19 +00:00 
			
		
		
		
	 7064c38e53
			
		
	
	
		7064c38e53
		
			
		
	
	
	
		
			
	
		
	
	
		
			Some checks are pending
		
		
	
	CI / Determine changes (push) Waiting to run
				
			CI / cargo fmt (push) Waiting to run
				
			CI / cargo clippy (push) Blocked by required conditions
				
			CI / cargo test (linux, release) (push) Blocked by required conditions
				
			CI / cargo test (windows) (push) Blocked by required conditions
				
			CI / cargo test (wasm) (push) Blocked by required conditions
				
			CI / cargo build (release) (push) Waiting to run
				
			CI / cargo build (msrv) (push) Blocked by required conditions
				
			CI / cargo fuzz build (push) Blocked by required conditions
				
			CI / fuzz parser (push) Blocked by required conditions
				
			CI / test scripts (push) Blocked by required conditions
				
			CI / ecosystem (push) Blocked by required conditions
				
			CI / Fuzz for new ty panics (push) Blocked by required conditions
				
			CI / cargo shear (push) Blocked by required conditions
				
			CI / ty completion evaluation (push) Blocked by required conditions
				
			CI / python package (push) Waiting to run
				
			CI / pre-commit (push) Waiting to run
				
			CI / mkdocs (push) Waiting to run
				
			CI / cargo test (linux) (push) Blocked by required conditions
				
			CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
				
			CI / benchmarks walltime (small|large) (push) Blocked by required conditions
				
			CI / formatter instabilities and black similarity (push) Blocked by required conditions
				
			CI / test ruff-lsp (push) Blocked by required conditions
				
			CI / check playground (push) Blocked by required conditions
				
			CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
				
			CI / benchmarks instrumented (ty) (push) Blocked by required conditions
				
			[ty Playground] Release / publish (push) Waiting to run
				
			
		
			
				
	
	
		
			655 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			655 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Method Resolution Order tests
 | |
| 
 | |
| Tests that assert that we can infer the correct type for a class's `__mro__` attribute.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| For documentation on method resolution orders, see:
 | |
| 
 | |
| - <https://docs.python.org/3/glossary.html#term-method-resolution-order>
 | |
| - <https://docs.python.org/3/howto/mro.html#python-2-3-mro>
 | |
| 
 | |
| ## No bases
 | |
| 
 | |
| ```py
 | |
| class C: ...
 | |
| 
 | |
| reveal_type(C.__mro__)  # revealed: tuple[<class 'C'>, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## The special case: `object` itself
 | |
| 
 | |
| ```py
 | |
| reveal_type(object.__mro__)  # revealed: tuple[<class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Explicit inheritance from `object`
 | |
| 
 | |
| ```py
 | |
| class C(object): ...
 | |
| 
 | |
| reveal_type(C.__mro__)  # revealed: tuple[<class 'C'>, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Explicit inheritance from non-`object` single base
 | |
| 
 | |
| ```py
 | |
| class A: ...
 | |
| class B(A): ...
 | |
| 
 | |
| reveal_type(B.__mro__)  # revealed: tuple[<class 'B'>, <class 'A'>, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Linearization of multiple bases
 | |
| 
 | |
| ```py
 | |
| class A: ...
 | |
| class B: ...
 | |
| class C(A, B): ...
 | |
| 
 | |
| reveal_type(C.__mro__)  # revealed: tuple[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Complex diamond inheritance (1)
 | |
| 
 | |
| This is "ex_2" from <https://docs.python.org/3/howto/mro.html#the-end>
 | |
| 
 | |
| ```py
 | |
| class O: ...
 | |
| class X(O): ...
 | |
| class Y(O): ...
 | |
| class A(X, Y): ...
 | |
| class B(Y, X): ...
 | |
| 
 | |
| reveal_type(A.__mro__)  # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(B.__mro__)  # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Complex diamond inheritance (2)
 | |
| 
 | |
| This is "ex_5" from <https://docs.python.org/3/howto/mro.html#the-end>
 | |
| 
 | |
| ```py
 | |
| class O: ...
 | |
| class F(O): ...
 | |
| class E(O): ...
 | |
| class D(O): ...
 | |
| class C(D, F): ...
 | |
| class B(D, E): ...
 | |
| class A(B, C): ...
 | |
| 
 | |
| # revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(C.__mro__)
 | |
| # revealed: tuple[<class 'B'>, <class 'D'>, <class 'E'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(B.__mro__)
 | |
| # revealed: tuple[<class 'A'>, <class 'B'>, <class 'C'>, <class 'D'>, <class 'E'>, <class 'F'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(A.__mro__)
 | |
| ```
 | |
| 
 | |
| ## Complex diamond inheritance (3)
 | |
| 
 | |
| This is "ex_6" from <https://docs.python.org/3/howto/mro.html#the-end>
 | |
| 
 | |
| ```py
 | |
| class O: ...
 | |
| class F(O): ...
 | |
| class E(O): ...
 | |
| class D(O): ...
 | |
| class C(D, F): ...
 | |
| class B(E, D): ...
 | |
| class A(B, C): ...
 | |
| 
 | |
| # revealed: tuple[<class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(C.__mro__)
 | |
| # revealed: tuple[<class 'B'>, <class 'E'>, <class 'D'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(B.__mro__)
 | |
| # revealed: tuple[<class 'A'>, <class 'B'>, <class 'E'>, <class 'C'>, <class 'D'>, <class 'F'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(A.__mro__)
 | |
| ```
 | |
| 
 | |
| ## Complex diamond inheritance (4)
 | |
| 
 | |
| This is "ex_9" from <https://docs.python.org/3/howto/mro.html#the-end>
 | |
| 
 | |
| ```py
 | |
| class O: ...
 | |
| class A(O): ...
 | |
| class B(O): ...
 | |
| class C(O): ...
 | |
| class D(O): ...
 | |
| class E(O): ...
 | |
| class K1(A, B, C): ...
 | |
| class K2(D, B, E): ...
 | |
| class K3(D, A): ...
 | |
| class Z(K1, K2, K3): ...
 | |
| 
 | |
| # revealed: tuple[<class 'K1'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(K1.__mro__)
 | |
| # revealed: tuple[<class 'K2'>, <class 'D'>, <class 'B'>, <class 'E'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(K2.__mro__)
 | |
| # revealed: tuple[<class 'K3'>, <class 'D'>, <class 'A'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(K3.__mro__)
 | |
| # revealed: tuple[<class 'Z'>, <class 'K1'>, <class 'K2'>, <class 'K3'>, <class 'D'>, <class 'A'>, <class 'B'>, <class 'C'>, <class 'E'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(Z.__mro__)
 | |
| ```
 | |
| 
 | |
| ## Inheritance from `Unknown`
 | |
| 
 | |
| ```py
 | |
| from does_not_exist import DoesNotExist  # error: [unresolved-import]
 | |
| 
 | |
| class A(DoesNotExist): ...
 | |
| class B: ...
 | |
| class C: ...
 | |
| class D(A, B, C): ...
 | |
| class E(B, C): ...
 | |
| class F(E, A): ...
 | |
| 
 | |
| reveal_type(A.__mro__)  # revealed: tuple[<class 'A'>, Unknown, <class 'object'>]
 | |
| reveal_type(D.__mro__)  # revealed: tuple[<class 'D'>, <class 'A'>, Unknown, <class 'B'>, <class 'C'>, <class 'object'>]
 | |
| reveal_type(E.__mro__)  # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>, <class 'object'>]
 | |
| # revealed: tuple[<class 'F'>, <class 'E'>, <class 'B'>, <class 'C'>, <class 'A'>, Unknown, <class 'object'>]
 | |
| reveal_type(F.__mro__)
 | |
| ```
 | |
| 
 | |
| ## Inheritance with intersections that include `Unknown`
 | |
| 
 | |
| An intersection that includes `Unknown` or `Any` is permitted as long as the intersection is not
 | |
| disjoint from `type`.
 | |
| 
 | |
| ```py
 | |
| from does_not_exist import DoesNotExist  # error: [unresolved-import]
 | |
| 
 | |
| reveal_type(DoesNotExist)  # revealed: Unknown
 | |
| 
 | |
| if hasattr(DoesNotExist, "__mro__"):
 | |
|     # TODO: this should be `Unknown & <Protocol with members '__mro__'>` or similar
 | |
|     # (The second part of the intersection is incorrectly simplified to `object` due to https://github.com/astral-sh/ty/issues/986)
 | |
|     reveal_type(DoesNotExist)  # revealed: Unknown
 | |
| 
 | |
|     class Foo(DoesNotExist): ...  # no error!
 | |
|     reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| 
 | |
| if not isinstance(DoesNotExist, type):
 | |
|     reveal_type(DoesNotExist)  # revealed: Unknown & ~type
 | |
| 
 | |
|     class Foo(DoesNotExist): ...  # error: [unsupported-base]
 | |
|     reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Inheritance from `type[Any]` and `type[Unknown]`
 | |
| 
 | |
| Inheritance from `type[Any]` and `type[Unknown]` is also permitted, in keeping with the gradual
 | |
| guarantee:
 | |
| 
 | |
| ```py
 | |
| from typing import Any
 | |
| from ty_extensions import Unknown, Intersection
 | |
| 
 | |
| def f(x: type[Any], y: Intersection[Unknown, type[Any]]):
 | |
|     class Foo(x): ...
 | |
|     reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
 | |
| 
 | |
|     class Bar(y): ...
 | |
|     reveal_type(Bar.__mro__)  # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` lists that cause errors at runtime
 | |
| 
 | |
| If the class's `__bases__` cause an exception to be raised at runtime and therefore the class
 | |
| creation to fail, we infer the class's `__mro__` as being `[<class>, Unknown, object]`:
 | |
| 
 | |
| ```py
 | |
| # error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Foo` with bases list `[<class 'object'>, <class 'int'>]`"
 | |
| class Foo(object, int): ...
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class Bar(Foo): ...
 | |
| 
 | |
| reveal_type(Bar.__mro__)  # revealed: tuple[<class 'Bar'>, <class 'Foo'>, Unknown, <class 'object'>]
 | |
| 
 | |
| # This is the `TypeError` at the bottom of "ex_2"
 | |
| # in the examples at <https://docs.python.org/3/howto/mro.html#the-end>
 | |
| class O: ...
 | |
| class X(O): ...
 | |
| class Y(O): ...
 | |
| class A(X, Y): ...
 | |
| class B(Y, X): ...
 | |
| 
 | |
| reveal_type(A.__mro__)  # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(B.__mro__)  # revealed: tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
 | |
| 
 | |
| # error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Z` with bases list `[<class 'A'>, <class 'B'>]`"
 | |
| class Z(A, B): ...
 | |
| 
 | |
| reveal_type(Z.__mro__)  # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class AA(Z): ...
 | |
| 
 | |
| reveal_type(AA.__mro__)  # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` includes a `Union`
 | |
| 
 | |
| <!-- snapshot-diagnostics -->
 | |
| 
 | |
| We don't support union types in a class's bases; a base must resolve to a single `ClassType`. If we
 | |
| find a union type in a class's bases, we infer the class's `__mro__` as being
 | |
| `[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
 | |
| 
 | |
| ```py
 | |
| def returns_bool() -> bool:
 | |
|     return True
 | |
| 
 | |
| class A: ...
 | |
| class B: ...
 | |
| 
 | |
| if returns_bool():
 | |
|     x = A
 | |
| else:
 | |
|     x = B
 | |
| 
 | |
| reveal_type(x)  # revealed: <class 'A'> | <class 'B'>
 | |
| 
 | |
| # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
 | |
| class Foo(x): ...
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` is a union of a dynamic type and valid bases
 | |
| 
 | |
| If a dynamic type such as `Any` or `Unknown` is one of the elements in the union, and all other
 | |
| types *would be* valid class bases, we do not emit an `invalid-base` or `unsupported-base`
 | |
| diagnostic, and we use the dynamic type as a base to prevent further downstream errors.
 | |
| 
 | |
| ```py
 | |
| from typing import Any
 | |
| 
 | |
| def _(flag: bool, any: Any):
 | |
|     if flag:
 | |
|         Base = any
 | |
|     else:
 | |
|         class Base: ...
 | |
| 
 | |
|     class Foo(Base): ...
 | |
|     reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` includes multiple `Union`s
 | |
| 
 | |
| ```py
 | |
| def returns_bool() -> bool:
 | |
|     return True
 | |
| 
 | |
| class A: ...
 | |
| class B: ...
 | |
| class C: ...
 | |
| class D: ...
 | |
| 
 | |
| if returns_bool():
 | |
|     x = A
 | |
| else:
 | |
|     x = B
 | |
| 
 | |
| if returns_bool():
 | |
|     y = C
 | |
| else:
 | |
|     y = D
 | |
| 
 | |
| reveal_type(x)  # revealed: <class 'A'> | <class 'B'>
 | |
| reveal_type(y)  # revealed: <class 'C'> | <class 'D'>
 | |
| 
 | |
| # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
 | |
| # error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
 | |
| class Foo(x, y): ...
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` lists that cause errors... now with `Union`s
 | |
| 
 | |
| ```py
 | |
| def returns_bool() -> bool:
 | |
|     return True
 | |
| 
 | |
| class O: ...
 | |
| class X(O): ...
 | |
| class Y(O): ...
 | |
| 
 | |
| if returns_bool():
 | |
|     foo = Y
 | |
| else:
 | |
|     foo = object
 | |
| 
 | |
| # error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
 | |
| class PossibleError(foo, X): ...
 | |
| 
 | |
| reveal_type(PossibleError.__mro__)  # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class A(X, Y): ...
 | |
| 
 | |
| reveal_type(A.__mro__)  # revealed: tuple[<class 'A'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>]
 | |
| 
 | |
| if returns_bool():
 | |
|     class B(X, Y): ...
 | |
| 
 | |
| else:
 | |
|     class B(Y, X): ...
 | |
| 
 | |
| # revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
 | |
| reveal_type(B.__mro__)
 | |
| 
 | |
| # error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
 | |
| class Z(A, B): ...
 | |
| 
 | |
| reveal_type(Z.__mro__)  # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` lists that include objects that are not instances of `type`
 | |
| 
 | |
| <!-- snapshot-diagnostics -->
 | |
| 
 | |
| ```py
 | |
| class Foo(2): ...  # error: [invalid-base]
 | |
| ```
 | |
| 
 | |
| A base that is not an instance of `type` but does have an `__mro_entries__` method will not raise an
 | |
| exception at runtime, so we issue `unsupported-base` rather than `invalid-base`:
 | |
| 
 | |
| ```py
 | |
| class Foo:
 | |
|     def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
 | |
|         return ()
 | |
| 
 | |
| class Bar(Foo()): ...  # error: [unsupported-base]
 | |
| ```
 | |
| 
 | |
| But for objects that have badly defined `__mro_entries__`, `invalid-base` is emitted rather than
 | |
| `unsupported-base`:
 | |
| 
 | |
| ```py
 | |
| class Bad1:
 | |
|     def __mro_entries__(self, bases, extra_arg):
 | |
|         return ()
 | |
| 
 | |
| class Bad2:
 | |
|     def __mro_entries__(self, bases) -> int:
 | |
|         return 42
 | |
| 
 | |
| class BadSub1(Bad1()): ...  # error: [invalid-base]
 | |
| class BadSub2(Bad2()): ...  # error: [invalid-base]
 | |
| ```
 | |
| 
 | |
| ## `__bases__` lists with duplicate bases
 | |
| 
 | |
| <!-- snapshot-diagnostics -->
 | |
| 
 | |
| ```py
 | |
| class Foo(str, str): ...  # error: [duplicate-base] "Duplicate base class `str`"
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class Spam: ...
 | |
| class Eggs: ...
 | |
| class Bar: ...
 | |
| class Baz: ...
 | |
| 
 | |
| # fmt: off
 | |
| 
 | |
| # error: [duplicate-base] "Duplicate base class `Spam`"
 | |
| # error: [duplicate-base] "Duplicate base class `Eggs`"
 | |
| class Ham(
 | |
|     Spam,
 | |
|     Eggs,
 | |
|     Bar,
 | |
|     Baz,
 | |
|     Spam,
 | |
|     Eggs,
 | |
| ): ...
 | |
| 
 | |
| # fmt: on
 | |
| 
 | |
| reveal_type(Ham.__mro__)  # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class Mushrooms: ...
 | |
| class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ...  # error: [duplicate-base]
 | |
| 
 | |
| reveal_type(Omelette.__mro__)  # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
 | |
| 
 | |
| # fmt: off
 | |
| 
 | |
| # error: [duplicate-base] "Duplicate base class `Eggs`"
 | |
| class VeryEggyOmelette(
 | |
|     Eggs,
 | |
|     Ham,
 | |
|     Spam,
 | |
|     Eggs,
 | |
|     Mushrooms,
 | |
|     Bar,
 | |
|     Eggs,
 | |
|     Baz,
 | |
|     Eggs,
 | |
| ): ...
 | |
| 
 | |
| # fmt: off
 | |
| ```
 | |
| 
 | |
| A `type: ignore` comment can suppress `duplicate-bases` errors if it is on the first or last line of
 | |
| the class "header":
 | |
| 
 | |
| ```py
 | |
| # fmt: off
 | |
| 
 | |
| class A: ...
 | |
| 
 | |
| class B(  # type: ignore[duplicate-base]
 | |
|     A,
 | |
|     A,
 | |
| ): ...
 | |
| 
 | |
| class C(
 | |
|     A,
 | |
|     A
 | |
| ):  # type: ignore[duplicate-base]
 | |
|     x: int
 | |
| 
 | |
| # fmt: on
 | |
| ```
 | |
| 
 | |
| But it will not suppress the error if it occurs in the class body, or on the duplicate base itself.
 | |
| The justification for this is that it is the class definition as a whole that will raise an
 | |
| exception at runtime, not a sub-expression in the class's bases list.
 | |
| 
 | |
| ```py
 | |
| # fmt: off
 | |
| 
 | |
| # error: [duplicate-base]
 | |
| class D(
 | |
|     A,
 | |
|     # error: [unused-ignore-comment]
 | |
|     A,  # type: ignore[duplicate-base]
 | |
| ): ...
 | |
| 
 | |
| # error: [duplicate-base]
 | |
| class E(
 | |
|     A,
 | |
|     A
 | |
| ):
 | |
|     # error: [unused-ignore-comment]
 | |
|     x: int  # type: ignore[duplicate-base]
 | |
| 
 | |
| # fmt: on
 | |
| ```
 | |
| 
 | |
| ## `__bases__` lists with duplicate `Unknown` bases
 | |
| 
 | |
| We do not emit errors on classes where multiple bases are inferred as `Unknown`, `Todo` or `Any`.
 | |
| Usually having duplicate bases in a bases list like this would cause us to emit a diagnostic;
 | |
| however, for gradual types this would break the
 | |
| [gradual guarantee](https://typing.python.org/en/latest/spec/concepts.html#the-gradual-guarantee):
 | |
| the dynamic base can usually be materialised to a type that would lead to a resolvable MRO.
 | |
| 
 | |
| ```py
 | |
| from unresolvable_module import UnknownBase1, UnknownBase2  # error: [unresolved-import]
 | |
| 
 | |
| reveal_type(UnknownBase1)  # revealed: Unknown
 | |
| reveal_type(UnknownBase2)  # revealed: Unknown
 | |
| 
 | |
| # no error here -- we respect the gradual guarantee:
 | |
| class Foo(UnknownBase1, UnknownBase2): ...
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| However, if there are duplicate class elements, we do emit an error, even if there are also multiple
 | |
| dynamic members. The following class definition will definitely fail, no matter what the dynamic
 | |
| bases materialize to:
 | |
| 
 | |
| ```py
 | |
| # error: [duplicate-base] "Duplicate base class `Foo`"
 | |
| class Bar(UnknownBase1, Foo, UnknownBase2, Foo): ...
 | |
| 
 | |
| reveal_type(Bar.__mro__)  # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Unrelated objects inferred as `Any`/`Unknown` do not have special `__mro__` attributes
 | |
| 
 | |
| ```py
 | |
| from does_not_exist import unknown_object  # error: [unresolved-import]
 | |
| 
 | |
| reveal_type(unknown_object)  # revealed: Unknown
 | |
| reveal_type(unknown_object.__mro__)  # revealed: Unknown
 | |
| ```
 | |
| 
 | |
| ## MROs of classes that use multiple inheritance with generic aliases and subscripted `Generic`
 | |
| 
 | |
| ```py
 | |
| from typing import Generic, TypeVar, Iterator
 | |
| 
 | |
| T = TypeVar("T")
 | |
| 
 | |
| class peekable(Generic[T], Iterator[T]): ...
 | |
| 
 | |
| # revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T@peekable]'>, <class 'Iterable[T@peekable]'>, typing.Protocol, typing.Generic, <class 'object'>]
 | |
| reveal_type(peekable.__mro__)
 | |
| 
 | |
| class peekable2(Iterator[T], Generic[T]): ...
 | |
| 
 | |
| # revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T@peekable2]'>, <class 'Iterable[T@peekable2]'>, typing.Protocol, typing.Generic, <class 'object'>]
 | |
| reveal_type(peekable2.__mro__)
 | |
| 
 | |
| class Base: ...
 | |
| class Intermediate(Base, Generic[T]): ...
 | |
| class Sub(Intermediate[T], Base): ...
 | |
| 
 | |
| # revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T@Sub]'>, <class 'Base'>, typing.Generic, <class 'object'>]
 | |
| reveal_type(Sub.__mro__)
 | |
| ```
 | |
| 
 | |
| ## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
 | |
| 
 | |
| <!-- snapshot-diagnostics -->
 | |
| 
 | |
| ```py
 | |
| from typing_extensions import Protocol, TypeVar, Generic
 | |
| 
 | |
| T = TypeVar("T")
 | |
| 
 | |
| class Foo(Protocol): ...
 | |
| class Bar(Protocol[T]): ...
 | |
| class Baz(Protocol[T], Foo, Bar[T]): ...  # error: [inconsistent-mro]
 | |
| ```
 | |
| 
 | |
| ## Classes that inherit from themselves
 | |
| 
 | |
| These are invalid, but we need to be able to handle them gracefully without panicking.
 | |
| 
 | |
| ```pyi
 | |
| class Foo(Foo): ...  # error: [cyclic-class-definition]
 | |
| 
 | |
| reveal_type(Foo)  # revealed: <class 'Foo'>
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class Bar: ...
 | |
| class Baz: ...
 | |
| class Boz(Bar, Baz, Boz): ...  # error: [cyclic-class-definition]
 | |
| 
 | |
| reveal_type(Boz)  # revealed: <class 'Boz'>
 | |
| reveal_type(Boz.__mro__)  # revealed: tuple[<class 'Boz'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Classes with indirect cycles in their MROs
 | |
| 
 | |
| These are similarly unlikely, but we still shouldn't crash:
 | |
| 
 | |
| ```pyi
 | |
| class Foo(Bar): ...  # error: [cyclic-class-definition]
 | |
| class Bar(Baz): ...  # error: [cyclic-class-definition]
 | |
| class Baz(Foo): ...  # error: [cyclic-class-definition]
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| reveal_type(Bar.__mro__)  # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
 | |
| reveal_type(Baz.__mro__)  # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Classes with cycles in their MROs, and multiple inheritance
 | |
| 
 | |
| ```pyi
 | |
| class Spam: ...
 | |
| class Foo(Bar): ...  # error: [cyclic-class-definition]
 | |
| class Bar(Baz): ...  # error: [cyclic-class-definition]
 | |
| class Baz(Foo, Spam): ...  # error: [cyclic-class-definition]
 | |
| 
 | |
| reveal_type(Foo.__mro__)  # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
 | |
| reveal_type(Bar.__mro__)  # revealed: tuple[<class 'Bar'>, Unknown, <class 'object'>]
 | |
| reveal_type(Baz.__mro__)  # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Classes with cycles in their MRO, and a sub-graph
 | |
| 
 | |
| ```pyi
 | |
| class FooCycle(BarCycle): ...  # error: [cyclic-class-definition]
 | |
| class Foo: ...
 | |
| class BarCycle(FooCycle): ...  # error: [cyclic-class-definition]
 | |
| class Bar(Foo): ...
 | |
| 
 | |
| # Avoid emitting the errors for these. The classes have cyclic superclasses,
 | |
| # but are not themselves cyclic...
 | |
| class Baz(Bar, BarCycle): ...
 | |
| class Spam(Baz): ...
 | |
| 
 | |
| reveal_type(FooCycle.__mro__)  # revealed: tuple[<class 'FooCycle'>, Unknown, <class 'object'>]
 | |
| reveal_type(BarCycle.__mro__)  # revealed: tuple[<class 'BarCycle'>, Unknown, <class 'object'>]
 | |
| reveal_type(Baz.__mro__)  # revealed: tuple[<class 'Baz'>, Unknown, <class 'object'>]
 | |
| reveal_type(Spam.__mro__)  # revealed: tuple[<class 'Spam'>, Unknown, <class 'object'>]
 | |
| ```
 | |
| 
 | |
| ## Other classes with possible cycles
 | |
| 
 | |
| ```toml
 | |
| [environment]
 | |
| python-version = "3.13"
 | |
| ```
 | |
| 
 | |
| ```pyi
 | |
| class C(C.a): ...
 | |
| reveal_type(C.__class__)  # revealed: <class 'type'>
 | |
| reveal_type(C.__mro__)  # revealed: tuple[<class 'C'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class D(D.a):
 | |
|     a: D
 | |
| reveal_type(D.__class__)  # revealed: <class 'type'>
 | |
| reveal_type(D.__mro__)  # revealed: tuple[<class 'D'>, Unknown, <class 'object'>]
 | |
| 
 | |
| class E[T](E.a): ...
 | |
| reveal_type(E.__class__)  # revealed: <class 'type'>
 | |
| reveal_type(E.__mro__)  # revealed: tuple[<class 'E[Unknown]'>, Unknown, typing.Generic, <class 'object'>]
 | |
| 
 | |
| class F[T](F(), F): ...  # error: [cyclic-class-definition]
 | |
| reveal_type(F.__class__)  # revealed: type[Unknown]
 | |
| reveal_type(F.__mro__)  # revealed: tuple[<class 'F[Unknown]'>, Unknown, <class 'object'>]
 | |
| ```
 |