[ty] Do not carry the generic context of Protocol or Generic in the ClassBase enum (#17989)
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / cargo build (release) (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (windows) (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks (push) Has been cancelled

## Summary

It doesn't seem to be necessary for our generics implementation to carry
the `GenericContext` in the `ClassBase` variants. Removing it simplifies
the code, fixes many TODOs about `Generic` or `Protocol` appearing
multiple times in MROs when each should only appear at most once, and
allows us to more accurately detect runtime errors that occur due to
`Generic` or `Protocol` appearing multiple times in a class's bases.

In order to remove the `GenericContext` from the `ClassBase` variant, it
turns out to be necessary to emulate
`typing._GenericAlias.__mro_entries__`, or we end up with a large number
of false-positive `inconsistent-mro` errors. This PR therefore also does
that.

Lastly, this PR fixes the inferred MROs of PEP-695 generic classes,
which implicitly inherit from `Generic` even if they have no explicit
bases.

## Test Plan

mdtests
This commit is contained in:
Alex Waygood 2025-05-22 21:37:03 -04:00 committed by GitHub
parent 6c0a59ea78
commit d02c9ada5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 271 additions and 215 deletions

108
crates/ty/docs/rules.md generated
View file

@ -50,7 +50,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L90) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91)
</details> </details>
## `conflicting-argument-forms` ## `conflicting-argument-forms`
@ -81,7 +81,7 @@ f(int) # error
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L134) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135)
</details> </details>
## `conflicting-declarations` ## `conflicting-declarations`
@ -111,7 +111,7 @@ a = 1
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161)
</details> </details>
## `conflicting-metaclass` ## `conflicting-metaclass`
@ -142,7 +142,7 @@ class C(A, B): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186)
</details> </details>
## `cyclic-class-definition` ## `cyclic-class-definition`
@ -173,7 +173,7 @@ class B(A): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212)
</details> </details>
## `duplicate-base` ## `duplicate-base`
@ -199,7 +199,7 @@ class B(A, A): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L255) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256)
</details> </details>
## `escape-character-in-forward-annotation` ## `escape-character-in-forward-annotation`
@ -336,7 +336,7 @@ TypeError: multiple bases have instance lay-out conflict
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L276) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277)
</details> </details>
## `inconsistent-mro` ## `inconsistent-mro`
@ -365,7 +365,7 @@ class C(A, B): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L362) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363)
</details> </details>
## `index-out-of-bounds` ## `index-out-of-bounds`
@ -390,7 +390,7 @@ t[3] # IndexError: tuple index out of range
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L386) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387)
</details> </details>
## `invalid-argument-type` ## `invalid-argument-type`
@ -416,7 +416,7 @@ func("foo") # error: [invalid-argument-type]
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L406) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407)
</details> </details>
## `invalid-assignment` ## `invalid-assignment`
@ -443,7 +443,7 @@ a: int = ''
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L446) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447)
</details> </details>
## `invalid-attribute-access` ## `invalid-attribute-access`
@ -476,7 +476,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395)
</details> </details>
## `invalid-base` ## `invalid-base`
@ -499,7 +499,7 @@ class A(42): ... # error: [invalid-base]
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L468) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469)
</details> </details>
## `invalid-context-manager` ## `invalid-context-manager`
@ -525,7 +525,7 @@ with 1:
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L519) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520)
</details> </details>
## `invalid-declaration` ## `invalid-declaration`
@ -553,7 +553,7 @@ a: str
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L540) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541)
</details> </details>
## `invalid-exception-caught` ## `invalid-exception-caught`
@ -594,7 +594,7 @@ except ZeroDivisionError:
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L563) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564)
</details> </details>
## `invalid-generic-class` ## `invalid-generic-class`
@ -625,7 +625,7 @@ class C[U](Generic[T]): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L599) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600)
</details> </details>
## `invalid-legacy-type-variable` ## `invalid-legacy-type-variable`
@ -658,7 +658,7 @@ def f(t: TypeVar("U")): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626)
</details> </details>
## `invalid-metaclass` ## `invalid-metaclass`
@ -690,7 +690,7 @@ class B(metaclass=f): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L674) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675)
</details> </details>
## `invalid-overload` ## `invalid-overload`
@ -738,7 +738,7 @@ def foo(x: int) -> int: ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L701) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
</details> </details>
## `invalid-parameter-default` ## `invalid-parameter-default`
@ -763,7 +763,7 @@ def f(a: int = ''): ...
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L744) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745)
</details> </details>
## `invalid-protocol` ## `invalid-protocol`
@ -796,7 +796,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335)
</details> </details>
## `invalid-raise` ## `invalid-raise`
@ -844,7 +844,7 @@ def g():
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
</details> </details>
## `invalid-return-type` ## `invalid-return-type`
@ -868,7 +868,7 @@ def func() -> int:
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428)
</details> </details>
## `invalid-super-argument` ## `invalid-super-argument`
@ -912,7 +912,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L807) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808)
</details> </details>
## `invalid-syntax-in-forward-annotation` ## `invalid-syntax-in-forward-annotation`
@ -952,7 +952,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
</details> </details>
## `invalid-type-checking-constant` ## `invalid-type-checking-constant`
@ -981,7 +981,7 @@ TYPE_CHECKING = ''
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847)
</details> </details>
## `invalid-type-form` ## `invalid-type-form`
@ -1010,7 +1010,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L870) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
</details> </details>
## `invalid-type-variable-constraints` ## `invalid-type-variable-constraints`
@ -1044,7 +1044,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895)
</details> </details>
## `missing-argument` ## `missing-argument`
@ -1068,7 +1068,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924)
</details> </details>
## `no-matching-overload` ## `no-matching-overload`
@ -1096,7 +1096,7 @@ func("string") # error: [no-matching-overload]
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L942) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943)
</details> </details>
## `non-subscriptable` ## `non-subscriptable`
@ -1119,7 +1119,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L965) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966)
</details> </details>
## `not-iterable` ## `not-iterable`
@ -1144,7 +1144,7 @@ for i in 34: # TypeError: 'int' object is not iterable
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984)
</details> </details>
## `parameter-already-assigned` ## `parameter-already-assigned`
@ -1170,7 +1170,7 @@ f(1, x=2) # Error raised here
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035)
</details> </details>
## `raw-string-type-annotation` ## `raw-string-type-annotation`
@ -1229,7 +1229,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371)
</details> </details>
## `subclass-of-final-class` ## `subclass-of-final-class`
@ -1257,7 +1257,7 @@ class B(A): ... # Error raised here
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1125) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126)
</details> </details>
## `too-many-positional-arguments` ## `too-many-positional-arguments`
@ -1283,7 +1283,7 @@ f("foo") # Error raised here
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1170) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171)
</details> </details>
## `type-assertion-failure` ## `type-assertion-failure`
@ -1310,7 +1310,7 @@ def _(x: int):
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1148) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149)
</details> </details>
## `unavailable-implicit-super-arguments` ## `unavailable-implicit-super-arguments`
@ -1354,7 +1354,7 @@ class A:
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1191) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192)
</details> </details>
## `unknown-argument` ## `unknown-argument`
@ -1380,7 +1380,7 @@ f(x=1, y=2) # Error raised here
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1248) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249)
</details> </details>
## `unresolved-attribute` ## `unresolved-attribute`
@ -1407,7 +1407,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270)
</details> </details>
## `unresolved-import` ## `unresolved-import`
@ -1431,7 +1431,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292)
</details> </details>
## `unresolved-reference` ## `unresolved-reference`
@ -1455,7 +1455,7 @@ print(x) # NameError: name 'x' is not defined
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1310) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311)
</details> </details>
## `unsupported-bool-conversion` ## `unsupported-bool-conversion`
@ -1491,7 +1491,7 @@ b1 < b2 < b1 # exception raised here
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1003) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004)
</details> </details>
## `unsupported-operator` ## `unsupported-operator`
@ -1518,7 +1518,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330)
</details> </details>
## `zero-stepsize-in-slice` ## `zero-stepsize-in-slice`
@ -1542,7 +1542,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352)
</details> </details>
## `invalid-ignore-comment` ## `invalid-ignore-comment`
@ -1598,7 +1598,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1055) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056)
</details> </details>
## `possibly-unbound-implicit-call` ## `possibly-unbound-implicit-call`
@ -1629,7 +1629,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L108) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109)
</details> </details>
## `possibly-unbound-import` ## `possibly-unbound-import`
@ -1660,7 +1660,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078)
</details> </details>
## `redundant-cast` ## `redundant-cast`
@ -1686,7 +1686,7 @@ cast(int, f()) # Redundant
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1422) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423)
</details> </details>
## `undefined-reveal` ## `undefined-reveal`
@ -1709,7 +1709,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231)
</details> </details>
## `unknown-rule` ## `unknown-rule`
@ -1777,7 +1777,7 @@ class D(C): ... # error: [unsupported-base]
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L486) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487)
</details> </details>
## `division-by-zero` ## `division-by-zero`
@ -1800,7 +1800,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238)
</details> </details>
## `possibly-unresolved-reference` ## `possibly-unresolved-reference`
@ -1827,7 +1827,7 @@ print(x) # NameError: name 'x' is not defined
### Links ### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) * [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1103) * [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104)
</details> </details>
## `unused-ignore-comment` ## `unused-ignore-comment`

View file

@ -81,23 +81,22 @@ import typing
class ListSubclass(typing.List): ... class ListSubclass(typing.List): ...
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(ListSubclass.__mro__) reveal_type(ListSubclass.__mro__)
class DictSubclass(typing.Dict): ... class DictSubclass(typing.Dict): ...
# TODO: should not have multiple `Generic[]` elements # revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(DictSubclass.__mro__) reveal_type(DictSubclass.__mro__)
class SetSubclass(typing.Set): ... class SetSubclass(typing.Set): ...
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(SetSubclass.__mro__) reveal_type(SetSubclass.__mro__)
class FrozenSetSubclass(typing.FrozenSet): ... class FrozenSetSubclass(typing.FrozenSet): ...
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(FrozenSetSubclass.__mro__) reveal_type(FrozenSetSubclass.__mro__)
#################### ####################
@ -106,30 +105,26 @@ reveal_type(FrozenSetSubclass.__mro__)
class ChainMapSubclass(typing.ChainMap): ... class ChainMapSubclass(typing.ChainMap): ...
# TODO: should not have multiple `Generic[]` elements # revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(ChainMapSubclass.__mro__) reveal_type(ChainMapSubclass.__mro__)
class CounterSubclass(typing.Counter): ... class CounterSubclass(typing.Counter): ...
# TODO: Should have one `Generic[]` element, not three(!) # revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
reveal_type(CounterSubclass.__mro__) reveal_type(CounterSubclass.__mro__)
class DefaultDictSubclass(typing.DefaultDict): ... class DefaultDictSubclass(typing.DefaultDict): ...
# TODO: Should not have multiple `Generic[]` elements # revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(DefaultDictSubclass.__mro__) reveal_type(DefaultDictSubclass.__mro__)
class DequeSubclass(typing.Deque): ... class DequeSubclass(typing.Deque): ...
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(DequeSubclass.__mro__) reveal_type(DequeSubclass.__mro__)
class OrderedDictSubclass(typing.OrderedDict): ... class OrderedDictSubclass(typing.OrderedDict): ...
# TODO: Should not have multiple `Generic[]` elements # revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
reveal_type(OrderedDictSubclass.__mro__) reveal_type(OrderedDictSubclass.__mro__)
``` ```

View file

@ -24,9 +24,7 @@ class:
```py ```py
class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base] class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base]
class AlsoBad(Generic[T], Generic[S]): ... # error: [duplicate-base]
# TODO: should emit an error (fails at runtime)
class AlsoBad(Generic[T], Generic[S]): ...
``` ```
You cannot use the same typevar more than once. You cannot use the same typevar more than once.

View file

@ -527,6 +527,45 @@ reveal_type(unknown_object) # revealed: Unknown
reveal_type(unknown_object.__mro__) # 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]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(peekable.__mro__)
class peekable2(Iterator[T], Generic[T]): ...
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, 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]'>, <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 ## Classes that inherit from themselves
These are invalid, but we need to be able to handle them gracefully without panicking. These are invalid, but we need to be able to handle them gracefully without panicking.

View file

@ -67,12 +67,10 @@ It's an error to include both bare `Protocol` and subscripted `Protocol[]` in th
simultaneously: simultaneously:
```py ```py
# TODO: should emit a `[duplicate-bases]` error here: class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
class DuplicateBases(Protocol, Protocol[T]):
x: T x: T
# TODO: should not have `Protocol` or `Generic` multiple times # revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>]
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], <class 'object'>]
reveal_type(DuplicateBases.__mro__) reveal_type(DuplicateBases.__mro__)
``` ```

View file

@ -0,0 +1,37 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: mro.md - Method Resolution Order tests - Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import Protocol, TypeVar, Generic
2 |
3 | T = TypeVar("T")
4 |
5 | class Foo(Protocol): ...
6 | class Bar(Protocol[T]): ...
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
```
# Diagnostics
```
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], <class 'Foo'>, <class 'Bar[T]'>]`
--> src/mdtest_snippet.py:7:1
|
5 | class Foo(Protocol): ...
6 | class Bar(Protocol[T]): ...
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `inconsistent-mro` is enabled by default
```

View file

@ -16,7 +16,7 @@ class Foo[T]: ...
class Bar(Foo[Bar]): ... class Bar(Foo[Bar]): ...
reveal_type(Bar) # revealed: <class 'Bar'> reveal_type(Bar) # revealed: <class 'Bar'>
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, <class 'object'>] reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>]
``` ```
## Access to attributes declared in stubs ## Access to attributes declared in stubs

View file

@ -83,7 +83,7 @@ python-version = "3.9"
```py ```py
class A(tuple[int, str]): ... class A(tuple[int, str]): ...
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(A.__mro__) reveal_type(A.__mro__)
``` ```
@ -114,6 +114,6 @@ from typing import Tuple
class C(Tuple): ... class C(Tuple): ...
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>] # revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(C.__mro__) reveal_type(C.__mro__)
``` ```

View file

@ -598,6 +598,10 @@ impl<'db> Type<'db> {
matches!(self, Type::Dynamic(DynamicType::Todo(_))) matches!(self, Type::Dynamic(DynamicType::Todo(_)))
} }
pub const fn is_generic_alias(&self) -> bool {
matches!(self, Type::GenericAlias(_))
}
/// Replace references to the class `class` with a self-reference marker. This is currently /// Replace references to the class `class` with a self-reference marker. This is currently
/// used for recursive protocols, but could probably be extended to self-referential type- /// used for recursive protocols, but could probably be extended to self-referential type-
/// aliases and similar. /// aliases and similar.

View file

@ -223,6 +223,10 @@ impl<'db> ClassType<'db> {
} }
} }
pub(super) const fn is_generic(self) -> bool {
matches!(self, Self::Generic(_))
}
/// Returns the class literal and specialization for this class. For a non-generic class, this /// Returns the class literal and specialization for this class. For a non-generic class, this
/// is the class itself. For a generic alias, this is the alias's origin. /// is the class itself. For a generic alias, this is the alias's origin.
pub(crate) fn class_literal( pub(crate) fn class_literal(
@ -352,7 +356,7 @@ impl<'db> ClassType<'db> {
ClassBase::Dynamic(_) => false, ClassBase::Dynamic(_) => false,
// Protocol and Generic are not represented by a ClassType. // Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol(_) | ClassBase::Generic(_) => false, ClassBase::Protocol | ClassBase::Generic => false,
ClassBase::Class(base) => match (base, other) { ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
@ -390,7 +394,7 @@ impl<'db> ClassType<'db> {
ClassBase::Dynamic(_) => false, ClassBase::Dynamic(_) => false,
// Protocol and Generic are not represented by a ClassType. // Protocol and Generic are not represented by a ClassType.
ClassBase::Protocol(_) | ClassBase::Generic(_) => false, ClassBase::Protocol | ClassBase::Generic => false,
ClassBase::Class(base) => match (base, other) { ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
@ -602,11 +606,6 @@ impl<'db> ClassLiteral<'db> {
) )
} }
/// Return `true` if this class represents the builtin class `object`
pub(crate) fn is_object(self, db: &'db dyn Db) -> bool {
self.is_known(db, KnownClass::Object)
}
fn file(self, db: &dyn Db) -> File { fn file(self, db: &dyn Db) -> File {
self.body_scope(db).file(db) self.body_scope(db).file(db)
} }
@ -1068,7 +1067,7 @@ impl<'db> ClassLiteral<'db> {
for superclass in mro_iter { for superclass in mro_iter {
match superclass { match superclass {
ClassBase::Generic(_) | ClassBase::Protocol(_) => { ClassBase::Generic | ClassBase::Protocol => {
// Skip over these very special class bases that aren't really classes. // Skip over these very special class bases that aren't really classes.
} }
ClassBase::Dynamic(_) => { ClassBase::Dynamic(_) => {
@ -1427,7 +1426,7 @@ impl<'db> ClassLiteral<'db> {
for superclass in self.iter_mro(db, specialization) { for superclass in self.iter_mro(db, specialization) {
match superclass { match superclass {
ClassBase::Generic(_) | ClassBase::Protocol(_) => { ClassBase::Generic | ClassBase::Protocol => {
// Skip over these very special class bases that aren't really classes. // Skip over these very special class bases that aren't really classes.
} }
ClassBase::Dynamic(_) => { ClassBase::Dynamic(_) => {

View file

@ -1,5 +1,5 @@
use crate::Db; use crate::Db;
use crate::types::generics::{GenericContext, Specialization}; use crate::types::generics::Specialization;
use crate::types::{ use crate::types::{
ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type,
TypeMapping, todo_type, TypeMapping, todo_type,
@ -19,11 +19,11 @@ pub enum ClassBase<'db> {
Class(ClassType<'db>), Class(ClassType<'db>),
/// Although `Protocol` is not a class in typeshed's stubs, it is at runtime, /// Although `Protocol` is not a class in typeshed's stubs, it is at runtime,
/// and can appear in the MRO of a class. /// and can appear in the MRO of a class.
Protocol(Option<GenericContext<'db>>), Protocol,
/// Bare `Generic` cannot be subclassed directly in user code, /// Bare `Generic` cannot be subclassed directly in user code,
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`, /// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
/// `Protocol[T]`, or bare `Protocol`. /// `Protocol[T]`, or bare `Protocol`.
Generic(Option<GenericContext<'db>>), Generic,
} }
impl<'db> ClassBase<'db> { impl<'db> ClassBase<'db> {
@ -35,60 +35,18 @@ impl<'db> ClassBase<'db> {
match self { match self {
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
Self::Class(class) => Self::Class(class.normalized(db)), Self::Class(class) => Self::Class(class.normalized(db)),
Self::Protocol(generic_context) => { Self::Protocol | Self::Generic => self,
Self::Protocol(generic_context.map(|context| context.normalized(db)))
}
Self::Generic(generic_context) => {
Self::Generic(generic_context.map(|context| context.normalized(db)))
}
} }
} }
pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
struct Display<'db> {
base: ClassBase<'db>,
db: &'db dyn Db,
}
impl std::fmt::Display for Display<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.base {
ClassBase::Dynamic(dynamic) => dynamic.fmt(f),
ClassBase::Class(class @ ClassType::NonGeneric(_)) => {
write!(f, "<class '{}'>", class.name(self.db))
}
ClassBase::Class(ClassType::Generic(alias)) => {
write!(f, "<class '{}'>", alias.display(self.db))
}
ClassBase::Protocol(generic_context) => {
f.write_str("typing.Protocol")?;
if let Some(generic_context) = generic_context {
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}
ClassBase::Generic(generic_context) => {
f.write_str("typing.Generic")?;
if let Some(generic_context) = generic_context {
generic_context.display(self.db).fmt(f)?;
}
Ok(())
}
}
}
}
Display { base: self, db }
}
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
match self { match self {
ClassBase::Class(class) => class.name(db), ClassBase::Class(class) => class.name(db),
ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Any) => "Any",
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo", ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo",
ClassBase::Protocol(_) => "Protocol", ClassBase::Protocol => "Protocol",
ClassBase::Generic(_) => "Generic", ClassBase::Generic => "Generic",
} }
} }
@ -255,12 +213,8 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::Callable => { KnownInstanceType::Callable => {
Self::try_from_type(db, todo_type!("Support for Callable as a base class")) Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
} }
KnownInstanceType::Protocol(generic_context) => { KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol),
Some(ClassBase::Protocol(generic_context)) KnownInstanceType::Generic(_) => Some(ClassBase::Generic),
}
KnownInstanceType::Generic(generic_context) => {
Some(ClassBase::Generic(generic_context))
}
}, },
} }
} }
@ -268,14 +222,14 @@ impl<'db> ClassBase<'db> {
pub(super) fn into_class(self) -> Option<ClassType<'db>> { pub(super) fn into_class(self) -> Option<ClassType<'db>> {
match self { match self {
Self::Class(class) => Some(class), Self::Class(class) => Some(class),
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None, Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
} }
} }
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
match self { match self {
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)), Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self, Self::Dynamic(_) | Self::Generic | Self::Protocol => self,
} }
} }
@ -299,7 +253,7 @@ impl<'db> ClassBase<'db> {
.try_mro(db, specialization) .try_mro(db, specialization)
.is_err_and(MroError::is_cycle) .is_err_and(MroError::is_cycle)
} }
ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false, ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false,
} }
} }
@ -310,12 +264,8 @@ impl<'db> ClassBase<'db> {
additional_specialization: Option<Specialization<'db>>, additional_specialization: Option<Specialization<'db>>,
) -> impl Iterator<Item = ClassBase<'db>> { ) -> impl Iterator<Item = ClassBase<'db>> {
match self { match self {
ClassBase::Protocol(context) => { ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic),
ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context)) ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self),
}
ClassBase::Dynamic(_) | ClassBase::Generic(_) => {
ClassBaseMroIterator::length_2(db, self)
}
ClassBase::Class(class) => { ClassBase::Class(class) => {
ClassBaseMroIterator::from_class(db, class, additional_specialization) ClassBaseMroIterator::from_class(db, class, additional_specialization)
} }
@ -338,12 +288,8 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
match value { match value {
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
ClassBase::Class(class) => class.into(), ClassBase::Class(class) => class.into(),
ClassBase::Protocol(generic_context) => { ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)),
Type::KnownInstance(KnownInstanceType::Protocol(generic_context)) ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)),
}
ClassBase::Generic(generic_context) => {
Type::KnownInstance(KnownInstanceType::Generic(generic_context))
}
} }
} }
} }

View file

@ -13,6 +13,7 @@ use crate::types::string_annotation::{
}; };
use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral};
use crate::{Program, PythonVersionWithSource, declare_lint}; use crate::{Program, PythonVersionWithSource, declare_lint};
use itertools::Itertools;
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
use ruff_db::files::system_path_to_file; use ruff_db::files::system_path_to_file;
use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_python_ast::{self as ast, AnyNodeRef};
@ -1698,10 +1699,7 @@ pub(super) fn report_implicit_return_type(
let Some(class) = enclosing_class_of_method else { let Some(class) = enclosing_class_of_method else {
return; return;
}; };
if class if class.iter_mro(db, None).contains(&ClassBase::Protocol) {
.iter_mro(db, None)
.any(|base| matches!(base, ClassBase::Protocol(_)))
{
diagnostic.info( diagnostic.info(
"Only functions in stub files, methods on protocol classes, \ "Only functions in stub files, methods on protocol classes, \
or methods with `@abstractmethod` are permitted to have empty bodies", or methods with `@abstractmethod` are permitted to have empty bodies",

View file

@ -7529,7 +7529,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if !value_ty.into_class_literal().is_some_and(|class| { if !value_ty.into_class_literal().is_some_and(|class| {
class class
.iter_mro(self.db(), None) .iter_mro(self.db(), None)
.any(|base| matches!(base, ClassBase::Generic(_))) .contains(&ClassBase::Generic)
}) { }) {
report_non_subscriptable( report_non_subscriptable(
&self.context, &self.context,

View file

@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher;
use crate::Db; use crate::Db;
use crate::types::class_base::ClassBase; use crate::types::class_base::ClassBase;
use crate::types::generics::Specialization; use crate::types::generics::Specialization;
use crate::types::{ClassLiteral, ClassType, Type}; use crate::types::{ClassLiteral, ClassType, KnownInstanceType, Type};
/// The inferred method resolution order of a given class. /// The inferred method resolution order of a given class.
/// ///
@ -48,12 +48,12 @@ impl<'db> Mro<'db> {
/// [`super::infer::TypeInferenceBuilder::infer_region_scope`].) /// [`super::infer::TypeInferenceBuilder::infer_region_scope`].)
pub(super) fn of_class( pub(super) fn of_class(
db: &'db dyn Db, db: &'db dyn Db,
class: ClassLiteral<'db>, class_literal: ClassLiteral<'db>,
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
) -> Result<Self, MroError<'db>> { ) -> Result<Self, MroError<'db>> {
Self::of_class_impl(db, class, specialization).map_err(|err| { let class = class_literal.apply_optional_specialization(db, specialization);
err.into_mro_error(db, class.apply_optional_specialization(db, specialization)) Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization)
}) .map_err(|err| err.into_mro_error(db, class))
} }
pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self { pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self {
@ -66,17 +66,16 @@ impl<'db> Mro<'db> {
fn of_class_impl( fn of_class_impl(
db: &'db dyn Db, db: &'db dyn Db,
class: ClassLiteral<'db>, class: ClassType<'db>,
bases: &[Type<'db>],
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
) -> Result<Self, MroErrorKind<'db>> { ) -> Result<Self, MroErrorKind<'db>> {
let class_type = class.apply_optional_specialization(db, specialization); match bases {
match class.explicit_bases(db) {
// `builtins.object` is the special case: // `builtins.object` is the special case:
// the only class in Python that has an MRO with length <2 // the only class in Python that has an MRO with length <2
[] if class.is_object(db) => Ok(Self::from([ [] if class.is_object(db) => Ok(Self::from([
// object is not generic, so the default specialization should be a no-op // object is not generic, so the default specialization should be a no-op
ClassBase::Class(class_type), ClassBase::Class(class),
])), ])),
// All other classes in Python have an MRO with length >=2. // All other classes in Python have an MRO with length >=2.
@ -92,44 +91,82 @@ impl<'db> Mro<'db> {
// >>> Foo.__mro__ // >>> Foo.__mro__
// (<class '__main__.Foo'>, <class 'object'>) // (<class '__main__.Foo'>, <class 'object'>)
// ``` // ```
[] => Ok(Self::from([ [] => {
ClassBase::Class(class_type), // e.g. `class Foo[T]: ...` implicitly has `Generic` inserted into its bases
ClassBase::object(db), if class.is_generic() {
])), Ok(Self::from([
ClassBase::Class(class),
ClassBase::Generic,
ClassBase::object(db),
]))
} else {
Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)]))
}
}
// Fast path for a class that has only a single explicit base. // Fast path for a class that has only a single explicit base.
// //
// This *could* theoretically be handled by the final branch below, // This *could* theoretically be handled by the final branch below,
// but it's a common case (i.e., worth optimizing for), // but it's a common case (i.e., worth optimizing for),
// and the `c3_merge` function requires lots of allocations. // and the `c3_merge` function requires lots of allocations.
[single_base] => ClassBase::try_from_type(db, *single_base).map_or_else( [single_base]
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), if !matches!(
|single_base| { single_base,
if single_base.has_cyclic_mro(db) { Type::GenericAlias(_)
Err(MroErrorKind::InheritanceCycle) | Type::KnownInstance(
} else { KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_)
Ok(std::iter::once(ClassBase::Class( )
class.apply_optional_specialization(db, specialization), ) =>
)) {
.chain(single_base.mro(db, specialization)) ClassBase::try_from_type(db, *single_base).map_or_else(
.collect()) || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
} |single_base| {
}, if single_base.has_cyclic_mro(db) {
), Err(MroErrorKind::InheritanceCycle)
} else {
Ok(std::iter::once(ClassBase::Class(class))
.chain(single_base.mro(db, specialization))
.collect())
}
},
)
}
// The class has multiple explicit bases. // The class has multiple explicit bases.
// //
// We'll fallback to a full implementation of the C3-merge algorithm to determine // We'll fallback to a full implementation of the C3-merge algorithm to determine
// what MRO Python will give this class at runtime // what MRO Python will give this class at runtime
// (if an MRO is indeed resolvable at all!) // (if an MRO is indeed resolvable at all!)
multiple_bases => { original_bases => {
let mut valid_bases = vec![]; let mut resolved_bases = vec![];
let mut invalid_bases = vec![]; let mut invalid_bases = vec![];
for (i, base) in multiple_bases.iter().enumerate() { for (i, base) in original_bases.iter().enumerate() {
match ClassBase::try_from_type(db, *base) { // This emulates the behavior of `typing._GenericAlias.__mro_entries__` at
Some(valid_base) => valid_bases.push(valid_base), // <https://github.com/python/cpython/blob/ad42dc1909bdf8ec775b63fb22ed48ff42797a17/Lib/typing.py#L1487-L1500>.
None => invalid_bases.push((i, *base)), //
// Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere
// (see `infer::TypeInferenceBuilder::check_class_definitions`),
// which is why we only care about `KnownInstanceType::Generic(Some(_))`,
// not `KnownInstanceType::Generic(None)`.
if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base {
if original_bases
.contains(&Type::KnownInstance(KnownInstanceType::Protocol(None)))
{
continue;
}
if original_bases[i + 1..]
.iter()
.any(|b| b.is_generic_alias() && b != base)
{
continue;
}
resolved_bases.push(ClassBase::Generic);
} else {
match ClassBase::try_from_type(db, *base) {
Some(valid_base) => resolved_bases.push(valid_base),
None => invalid_bases.push((i, *base)),
}
} }
} }
@ -137,15 +174,15 @@ impl<'db> Mro<'db> {
return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice())); return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice()));
} }
let mut seqs = vec![VecDeque::from([ClassBase::Class(class_type)])]; let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])];
for base in &valid_bases { for base in &resolved_bases {
if base.has_cyclic_mro(db) { if base.has_cyclic_mro(db) {
return Err(MroErrorKind::InheritanceCycle); return Err(MroErrorKind::InheritanceCycle);
} }
seqs.push(base.mro(db, specialization).collect()); seqs.push(base.mro(db, specialization).collect());
} }
seqs.push( seqs.push(
valid_bases resolved_bases
.iter() .iter()
.map(|base| base.apply_optional_specialization(db, specialization)) .map(|base| base.apply_optional_specialization(db, specialization))
.collect(), .collect(),
@ -161,8 +198,20 @@ impl<'db> Mro<'db> {
let mut base_to_indices: IndexMap<ClassBase<'db>, Vec<usize>, FxBuildHasher> = let mut base_to_indices: IndexMap<ClassBase<'db>, Vec<usize>, FxBuildHasher> =
IndexMap::default(); IndexMap::default();
for (index, base) in valid_bases.iter().enumerate() { // We need to iterate over `original_bases` here rather than `resolved_bases`
base_to_indices.entry(*base).or_default().push(index); // so that we get the correct index of the duplicate bases if there were any
// (`resolved_bases` may be a longer list than `original_bases`!). However, we
// need to use a `ClassBase` rather than a `Type` as the key type for the
// `base_to_indices` map so that a class such as
// `class Foo(Protocol[T], Protocol): ...` correctly causes us to emit a
// `duplicate-base` diagnostic (matching the runtime behaviour) rather than an
// `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as
// precise!).
for (index, base) in original_bases.iter().enumerate() {
let Some(base) = ClassBase::try_from_type(db, *base) else {
continue;
};
base_to_indices.entry(base).or_default().push(index);
} }
let mut errors = vec![]; let mut errors = vec![];
@ -175,9 +224,7 @@ impl<'db> Mro<'db> {
continue; continue;
} }
match base { match base {
ClassBase::Class(_) ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => {
| ClassBase::Generic(_)
| ClassBase::Protocol(_) => {
errors.push(DuplicateBaseError { errors.push(DuplicateBaseError {
duplicate_base: base, duplicate_base: base,
first_index: *first_index, first_index: *first_index,
@ -193,13 +240,10 @@ impl<'db> Mro<'db> {
if duplicate_bases.is_empty() { if duplicate_bases.is_empty() {
if duplicate_dynamic_bases { if duplicate_dynamic_bases {
Ok(Mro::from_error( Ok(Mro::from_error(db, class))
db,
class.apply_optional_specialization(db, specialization),
))
} else { } else {
Err(MroErrorKind::UnresolvableMro { Err(MroErrorKind::UnresolvableMro {
bases_list: valid_bases.into_boxed_slice(), bases_list: original_bases.iter().copied().collect(),
}) })
} }
} else { } else {
@ -378,7 +422,7 @@ pub(super) enum MroErrorKind<'db> {
/// The MRO is otherwise unresolvable through the C3-merge algorithm. /// The MRO is otherwise unresolvable through the C3-merge algorithm.
/// ///
/// See [`c3_merge`] for more details. /// See [`c3_merge`] for more details.
UnresolvableMro { bases_list: Box<[ClassBase<'db>]> }, UnresolvableMro { bases_list: Box<[Type<'db>]> },
} }
impl<'db> MroErrorKind<'db> { impl<'db> MroErrorKind<'db> {

View file

@ -152,13 +152,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(ClassBase::Class(_), _) => Ordering::Less, (ClassBase::Class(_), _) => Ordering::Less,
(_, ClassBase::Class(_)) => Ordering::Greater, (_, ClassBase::Class(_)) => Ordering::Greater,
(ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right), (ClassBase::Protocol, _) => Ordering::Less,
(ClassBase::Protocol(_), _) => Ordering::Less, (_, ClassBase::Protocol) => Ordering::Greater,
(_, ClassBase::Protocol(_)) => Ordering::Greater,
(ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right), (ClassBase::Generic, _) => Ordering::Less,
(ClassBase::Generic(_), _) => Ordering::Less, (_, ClassBase::Generic) => Ordering::Greater,
(_, ClassBase::Generic(_)) => Ordering::Greater,
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => { (ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
dynamic_elements_ordering(left, right) dynamic_elements_ordering(left, right)