Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Waygood
68bdf9fa88 [ty] Make special cases for subscript inference exhaustive 2026-01-14 11:47:19 +00:00
33 changed files with 676 additions and 1247 deletions

194
crates/ty/docs/rules.md generated
View File

@@ -8,7 +8,7 @@
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
</small>
@@ -80,7 +80,7 @@ def test(): -> "int":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141" target="_blank">View source</a>
</small>
@@ -104,7 +104,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.7">0.0.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159" target="_blank">View source</a>
</small>
@@ -135,7 +135,7 @@ def f(x: object):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210" target="_blank">View source</a>
</small>
@@ -167,7 +167,7 @@ f(int) # error
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236" target="_blank">View source</a>
</small>
@@ -198,7 +198,7 @@ a = 1
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L262" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261" target="_blank">View source</a>
</small>
@@ -230,7 +230,7 @@ class C(A, B): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L288" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287" target="_blank">View source</a>
</small>
@@ -262,7 +262,7 @@ class B(A): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L314" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
</small>
@@ -290,7 +290,7 @@ type B = A
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L358" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L357" target="_blank">View source</a>
</small>
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335" target="_blank">View source</a>
</small>
@@ -346,7 +346,7 @@ false positives it can produce.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L379" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L378" target="_blank">View source</a>
</small>
@@ -373,7 +373,7 @@ class B(A, A): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L400" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399" target="_blank">View source</a>
</small>
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625" target="_blank">View source</a>
</small>
@@ -559,7 +559,7 @@ class C(A, B): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L650" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649" target="_blank">View source</a>
</small>
@@ -579,47 +579,13 @@ t = (0, 1, 2)
t[3] # IndexError: tuple index out of range
```
## `ineffective-final`
<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.33">0.0.1-alpha.33</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ineffective-final" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1793" target="_blank">View source</a>
</small>
**What it does**
Checks for calls to `final()` that type checkers cannot interpret.
**Why is this bad?**
The `final()` function is designed to be used as a decorator. When called directly
as a function (e.g., `final(type(...))`), type checkers will not understand the
application of `final` and will not prevent subclassing.
**Example**
```python
from typing import final
# Incorrect: type checkers will not prevent subclassing
MyClass = final(type("MyClass", (), {}))
# Correct: use `final` as a decorator
@final
class MyClass: ...
```
## `instance-layout-conflict`
<small>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L432" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431" target="_blank">View source</a>
</small>
@@ -708,7 +674,7 @@ an atypical memory layout.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703" target="_blank">View source</a>
</small>
@@ -735,7 +701,7 @@ func("foo") # error: [invalid-argument-type]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L744" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L743" target="_blank">View source</a>
</small>
@@ -763,7 +729,7 @@ a: int = ''
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2151" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2122" target="_blank">View source</a>
</small>
@@ -797,7 +763,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765" target="_blank">View source</a>
</small>
@@ -833,7 +799,7 @@ asyncio.run(main())
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L796" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L795" target="_blank">View source</a>
</small>
@@ -857,7 +823,7 @@ class A(42): ... # error: [invalid-base]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L881" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L880" target="_blank">View source</a>
</small>
@@ -884,7 +850,7 @@ with 1:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L902" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901" target="_blank">View source</a>
</small>
@@ -913,7 +879,7 @@ a: str
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924" target="_blank">View source</a>
</small>
@@ -957,7 +923,7 @@ except ZeroDivisionError:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1821" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1792" target="_blank">View source</a>
</small>
@@ -999,7 +965,7 @@ class D(A):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2402" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2373" target="_blank">View source</a>
</small>
@@ -1043,7 +1009,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1003" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002" target="_blank">View source</a>
</small>
@@ -1081,7 +1047,7 @@ class D(Generic[U, T]): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.12">0.0.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-enum" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L961" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960" target="_blank">View source</a>
</small>
@@ -1160,7 +1126,7 @@ a = 20 / 0 # type: ignore
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L671" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L670" target="_blank">View source</a>
</small>
@@ -1199,7 +1165,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1033" target="_blank">View source</a>
</small>
@@ -1234,7 +1200,7 @@ def f(t: TypeVar("U")): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1131" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1130" target="_blank">View source</a>
</small>
@@ -1268,7 +1234,7 @@ class B(metaclass=f): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2304" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2275" target="_blank">View source</a>
</small>
@@ -1375,7 +1341,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L578" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L577" target="_blank">View source</a>
</small>
@@ -1429,7 +1395,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106" target="_blank">View source</a>
</small>
@@ -1459,7 +1425,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1158" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157" target="_blank">View source</a>
</small>
@@ -1509,7 +1475,7 @@ def foo(x: int) -> int: ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1257" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256" target="_blank">View source</a>
</small>
@@ -1535,7 +1501,7 @@ def f(a: int = ''): ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061" target="_blank">View source</a>
</small>
@@ -1566,7 +1532,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L514" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513" target="_blank">View source</a>
</small>
@@ -1600,7 +1566,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1277" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
</small>
@@ -1649,7 +1615,7 @@ def g():
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L725" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L724" target="_blank">View source</a>
</small>
@@ -1674,7 +1640,7 @@ def func() -> int:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1320" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1319" target="_blank">View source</a>
</small>
@@ -1770,7 +1736,7 @@ class C: ...
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.10">0.0.10</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-total-ordering" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2440" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2411" target="_blank">View source</a>
</small>
@@ -1816,7 +1782,7 @@ class MyClass:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085" target="_blank">View source</a>
</small>
@@ -1843,7 +1809,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1552" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1551" target="_blank">View source</a>
</small>
@@ -1890,7 +1856,7 @@ Bar[int] # error: too few arguments
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1359" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1358" target="_blank">View source</a>
</small>
@@ -1920,7 +1886,7 @@ TYPE_CHECKING = ''
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1383" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
</small>
@@ -1950,7 +1916,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
</small>
@@ -1984,7 +1950,7 @@ f(10) # Error
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1407" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1406" target="_blank">View source</a>
</small>
@@ -2018,7 +1984,7 @@ class C:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1462" target="_blank">View source</a>
</small>
@@ -2053,7 +2019,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.9">0.0.9</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-typed-dict-statement" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2279" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2250" target="_blank">View source</a>
</small>
@@ -2084,7 +2050,7 @@ class Foo(TypedDict):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1492" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491" target="_blank">View source</a>
</small>
@@ -2109,7 +2075,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2252" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2223" target="_blank">View source</a>
</small>
@@ -2142,7 +2108,7 @@ alice["age"] # KeyError
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1511" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1510" target="_blank">View source</a>
</small>
@@ -2171,7 +2137,7 @@ func("string") # error: [no-matching-overload]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1593" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1592" target="_blank">View source</a>
</small>
@@ -2197,7 +2163,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-subscriptable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1534" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1533" target="_blank">View source</a>
</small>
@@ -2221,7 +2187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1766" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765" target="_blank">View source</a>
</small>
@@ -2254,7 +2220,7 @@ class B(A):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
</small>
@@ -2281,7 +2247,7 @@ f(1, x=2) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2005" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
</small>
@@ -2308,7 +2274,7 @@ f(x=1) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1665" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664" target="_blank">View source</a>
</small>
@@ -2336,7 +2302,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L184" target="_blank">View source</a>
</small>
@@ -2368,7 +2334,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1687" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1686" target="_blank">View source</a>
</small>
@@ -2405,7 +2371,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1717" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716" target="_blank">View source</a>
</small>
@@ -2469,7 +2435,7 @@ def test(): -> "int":
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2179" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2150" target="_blank">View source</a>
</small>
@@ -2496,7 +2462,7 @@ cast(int, f()) # Redundant
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2127" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2098" target="_blank">View source</a>
</small>
@@ -2526,7 +2492,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1743" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1742" target="_blank">View source</a>
</small>
@@ -2555,7 +2521,7 @@ class B(A): ... # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1939" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1910" target="_blank">View source</a>
</small>
@@ -2589,7 +2555,7 @@ class F(NamedTuple):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1879" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1850" target="_blank">View source</a>
</small>
@@ -2616,7 +2582,7 @@ f("foo") # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1857" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1828" target="_blank">View source</a>
</small>
@@ -2644,7 +2610,7 @@ def _(x: int):
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1900" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1871" target="_blank">View source</a>
</small>
@@ -2690,7 +2656,7 @@ class A:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1966" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1937" target="_blank">View source</a>
</small>
@@ -2714,7 +2680,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1984" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1955" target="_blank">View source</a>
</small>
@@ -2741,7 +2707,7 @@ f(x=1, y=2) # Error raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2026" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
</small>
@@ -2769,7 +2735,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2200" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2171" target="_blank">View source</a>
</small>
@@ -2827,7 +2793,7 @@ def g():
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2048" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2019" target="_blank">View source</a>
</small>
@@ -2852,7 +2818,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2067" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2038" target="_blank">View source</a>
</small>
@@ -2877,7 +2843,7 @@ print(x) # NameError: name 'x' is not defined
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L814" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813" target="_blank">View source</a>
</small>
@@ -2916,7 +2882,7 @@ class D(C): ... # error: [unsupported-base]
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1613" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1612" target="_blank">View source</a>
</small>
@@ -2953,7 +2919,7 @@ b1 < b2 < b1 # exception raised here
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-dynamic-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846" target="_blank">View source</a>
</small>
@@ -2994,7 +2960,7 @@ def factory(base: type[Base]) -> type:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2086" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2057" target="_blank">View source</a>
</small>
@@ -3058,7 +3024,7 @@ to `false` to prevent this rule from reporting unused `type: ignore` comments.
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1201" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200" target="_blank">View source</a>
</small>
@@ -3121,7 +3087,7 @@ def foo(x: int | str) -> int | str:
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2108" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2079" target="_blank">View source</a>
</small>

View File

@@ -2585,7 +2585,7 @@ type<CURSOR>
assert_snapshot!(
test.type_signatures().skip_auto_import().build().snapshot(),
@"
@r"
type :: <class 'type'>
TypeError :: <class 'TypeError'>
",
@@ -8009,7 +8009,7 @@ my_list[0].remove<CURSOR>
);
assert_snapshot!(
builder.build().snapshot(),
@"
@r"
removeprefix
removesuffix
",
@@ -8030,7 +8030,7 @@ def f(x: Any | str):
);
assert_snapshot!(
builder.build().snapshot(),
@"
@r"
removeprefix
removesuffix
",
@@ -8052,7 +8052,7 @@ def f(x: Intersection[int, Any] | str):
);
assert_snapshot!(
builder.build().snapshot(),
@"
@r"
removeprefix
removesuffix
",

View File

@@ -9,7 +9,6 @@
use regex::Regex;
use ruff_python_trivia::{PythonWhitespace, leading_indentation};
use ruff_source_file::UniversalNewlines;
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::LazyLock;
@@ -350,37 +349,16 @@ fn render_markdown(docstring: &str) -> String {
| "versionchanged" | "version-changed" | "version-deprecated" | "deprecated"
| "version-removed" | "versionremoved",
) => {
// Map version directives to human-readable phrases (matching Sphinx output)
let pretty_directive = match directive.unwrap() {
"versionadded" | "version-added" => Cow::Borrowed("Added in version"),
"versionchanged" | "version-changed" => Cow::Borrowed("Changed in version"),
"deprecated" | "version-deprecated" => {
Cow::Borrowed("Deprecated since version")
}
"versionremoved" | "version-removed" => Cow::Borrowed("Removed in version"),
other => Cow::Owned(
other
.char_indices()
.map(|(index, c)| {
if index == 0 {
c.to_ascii_uppercase()
} else {
c
}
})
.collect(),
),
};
// Render the argument of things like `.. version-added:: 4.0`
let suffix = if let Some(lang) = lang {
format!(" {lang}")
format!(" *{lang}*")
} else {
String::new()
};
// We prepend without_directive here out of caution for preserving input.
// This is probably gibberish/invalid syntax? But it's a no-op in normal cases.
temp_owned_line = format!("**{without_directive}{pretty_directive}{suffix}:**");
temp_owned_line =
format!("**{without_directive}{}:**{suffix}", directive.unwrap());
line = temp_owned_line.as_str();
None
@@ -1098,7 +1076,7 @@ mod tests {
assert_snapshot!(docstring.render_markdown(), @r#"
The thing you need to understand is that computers are hard.
**Warning:**
**warning:**
&nbsp;&nbsp;&nbsp;&nbsp;Now listen here buckaroo you might have seen me say computers are hard,
&nbsp;&nbsp;&nbsp;&nbsp;and though "yeah I know computers are hard but NO you DON'T KNOW.
@@ -1137,12 +1115,12 @@ mod tests {
assert_snapshot!(docstring.render_markdown(), @"
Some much-updated docs
**Added in version 3.0:**
**version-added:** *3.0*
&nbsp;&nbsp;&nbsp;Function added
**Changed in version 4.0:**
**version-changed:** *4.0*
&nbsp;&nbsp;&nbsp;The `spam` argument was added
**Changed in version 4.1:**
**version-changed:** *4.1*
&nbsp;&nbsp;&nbsp;The `spam` argument is considered evil now.
&nbsp;&nbsp;&nbsp;You really shouldnt use it
@@ -1163,7 +1141,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
**wow this is some changes Deprecated since version 1.2.3:**
**wow this is some changes deprecated:** *1.2.3*
&nbsp;&nbsp;&nbsp;&nbsp;x = 2
");
}
@@ -1173,7 +1151,7 @@ mod tests {
fn explicit_markdown_block_with_ps1_contents() {
let docstring = r#"
My cool func:
```python
>>> thing.do_thing()
wow it did the thing
@@ -1184,7 +1162,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
```python
@@ -1201,7 +1179,7 @@ mod tests {
fn explicit_markdown_block_with_underscore_contents_tick() {
let docstring = r#"
My cool func:
`````python
x_y = thing_do();
``` # this should't close the fence!
@@ -1211,7 +1189,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
`````python
@@ -1227,7 +1205,7 @@ mod tests {
fn explicit_markdown_block_with_underscore_contents_tilde() {
let docstring = r#"
My cool func:
~~~~~python
x_y = thing_do();
~~~ # this should't close the fence!
@@ -1237,7 +1215,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~python
@@ -1255,7 +1233,7 @@ mod tests {
fn explicit_markdown_block_with_indent_tick() {
let docstring = r#"
My cool func...
Returns:
Some details
`````python
@@ -1268,7 +1246,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func...
Returns:
@@ -1289,7 +1267,7 @@ mod tests {
fn explicit_markdown_block_with_indent_tilde() {
let docstring = r#"
My cool func...
Returns:
Some details
~~~~~~python
@@ -1302,7 +1280,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func...
Returns:
@@ -1321,14 +1299,14 @@ mod tests {
fn explicit_markdown_block_with_unclosed_fence_tick() {
let docstring = r#"
My cool func:
````python
x_y = thing_do();
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
````python
@@ -1342,14 +1320,14 @@ mod tests {
fn explicit_markdown_block_with_unclosed_fence_tilde() {
let docstring = r#"
My cool func:
~~~~~python
x_y = thing_do();
"#;
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~python
@@ -1364,7 +1342,7 @@ mod tests {
fn explicit_markdown_block_messy_corners_tick() {
let docstring = r#"
My cool func:
``````we still think this is a codefence```
x_y = thing_do();
```````````` and are sloppy as heck with indentation and closing shrugggg
@@ -1372,7 +1350,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
``````we still think this is a codefence```
@@ -1387,7 +1365,7 @@ mod tests {
fn explicit_markdown_block_messy_corners_tilde() {
let docstring = r#"
My cool func:
~~~~~~we still think this is a codefence~~~
x_y = thing_do();
~~~~~~~~~~~~~ and are sloppy as heck with indentation and closing shrugggg
@@ -1395,7 +1373,7 @@ mod tests {
let docstring = Docstring::new(docstring.to_owned());
assert_snapshot!(docstring.render_markdown(), @"
assert_snapshot!(docstring.render_markdown(), @r"
My cool func:
~~~~~~we still think this is a codefence~~~

View File

@@ -1649,65 +1649,6 @@ Traceb<CURSOR>ackType
assert_snapshot!(test.goto_definition(), @"No goto target found");
}
/// goto-definition on a dynamic class literal (created via `type()`)
#[test]
fn goto_definition_dynamic_class_literal() {
let test = CursorTest::builder()
.source(
"main.py",
r#"
DynClass = type("DynClass", (), {})
x = DynCla<CURSOR>ss()
"#,
)
.build();
assert_snapshot!(test.goto_definition(), @r#"
info[goto-definition]: Go to definition
--> main.py:4:5
|
2 | DynClass = type("DynClass", (), {})
3 |
4 | x = DynClass()
| ^^^^^^^^ Clicking here
|
info: Found 2 definitions
--> main.py:2:1
|
2 | DynClass = type("DynClass", (), {})
| --------
3 |
4 | x = DynClass()
|
::: stdlib/builtins.pyi:137:9
|
135 | def __class__(self, type: type[Self], /) -> None: ...
136 | def __init__(self) -> None: ...
137 | def __new__(cls) -> Self: ...
| -------
138 | # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
139 | # Overriding them in subclasses has different semantics, even if the override has an identical signature.
|
"#);
}
/// goto-definition on a dangling dynamic class literal (not assigned to a variable)
#[test]
fn goto_definition_dangling_dynamic_class_literal() {
let test = CursorTest::builder()
.source(
"main.py",
r#"
class Foo(type("Ba<CURSOR>r", (), {})):
pass
"#,
)
.build();
assert_snapshot!(test.goto_definition(), @"No goto target found");
}
// TODO: Should only list `a: int`
#[test]
fn redeclarations() {

View File

@@ -20,13 +20,16 @@ class Base: ...
class Mixin: ...
# We synthesize a class type using the name argument
reveal_type(type("Foo", (), {})) # revealed: <class 'Foo'>
Foo = type("Foo", (), {})
reveal_type(Foo) # revealed: <class 'Foo'>
# With a single base class
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: <class 'Foo'>
Foo2 = type("Foo", (Base,), {"attr": 1})
reveal_type(Foo2) # revealed: <class 'Foo'>
# With multiple base classes
reveal_type(type("Foo", (Base, Mixin), {})) # revealed: <class 'Foo'>
Foo3 = type("Foo", (Base, Mixin), {})
reveal_type(Foo3) # revealed: <class 'Foo'>
# The inferred type is assignable to type[Base] since Foo inherits from Base
tests: list[type[Base]] = []
@@ -496,38 +499,39 @@ Other numbers of arguments are invalid:
```py
# error: [no-matching-overload] "No overload of class `type` matches arguments"
reveal_type(type("Foo", ())) # revealed: type[Unknown]
reveal_type(type("Foo", ())) # revealed: Unknown
# TODO: the keyword arguments for `Foo`/`Bar`/`Baz` here are invalid
# (none of them have base classes with `__init_subclass__` methods).
# (you cannot pass `metaclass=` to `type()`, and none of them have
# base classes with `__init_subclass__` methods),
# but `type[Unknown]` would be better than `Unknown` here
#
# The intent to create a new class is however clear,
# so we still infer these as class-literal types.
reveal_type(type("Foo", (), {}, weird_other_arg=42)) # revealed: <class 'Foo'>
reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: <class 'Bar'>
# You can't pass `metaclass=` to the `type()` constructor, but the intent is clear,
# so we infer `<class 'Baz'>` here rather than `type[Unknown]`
# error: [no-matching-overload] "No overload of class `type` matches arguments"
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: <class 'Baz'>
reveal_type(type("Foo", (), {}, weird_other_arg=42)) # revealed: Unknown
# error: [no-matching-overload] "No overload of class `type` matches arguments"
reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: Unknown
# error: [no-matching-overload] "No overload of class `type` matches arguments"
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: Unknown
```
The following calls are also invalid, due to incorrect argument types:
The following calls are also invalid, due to incorrect argument types.
Inline calls (not assigned to a variable) fall back to regular `type` overload matching, which
produces slightly different error messages than assigned dynamic class creation:
```py
class Base: ...
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `Literal[b"Foo"]`"
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
type(b"Foo", (), {})
# error: [invalid-argument-type] "Invalid argument to parameter 2 (`bases`) of `type()`: Expected `tuple[type, ...]`, found `<class 'Base'>`"
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
type("Foo", Base, {})
# error: 14 [invalid-base] "Invalid class base with type `Literal[1]`"
# error: 17 [invalid-base] "Invalid class base with type `Literal[2]`"
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
type("Foo", (1, 2), {})
# error: [invalid-argument-type] "Invalid argument to parameter 3 (`namespace`) of `type()`: Expected `dict[str, Any]`, found `dict[Unknown | bytes, Unknown | int]`"
# error: 22 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
type("Foo", (Base,), {b"attr": 1})
```
@@ -594,19 +598,6 @@ class Y(C, B): ...
Conflict = type("Conflict", (X, Y), {})
```
## MRO error highlighting (snapshot)
<!-- snapshot-diagnostics -->
This snapshot test documents the diagnostic highlighting range for dynamic class literals.
Currently, the entire `type()` call expression is highlighted:
```py
class A: ...
Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
```
## Metaclass conflicts
Metaclass conflicts are detected and reported:
@@ -862,77 +853,55 @@ def f(*args, **kwargs):
# Has a string first arg, but unknown additional args from *args
B = type("B", *args, **kwargs)
reveal_type(B) # revealed: type[Unknown]
# TODO: `type[Unknown]` would cause fewer false positives
reveal_type(B) # revealed: <class 'str'>
# Has string and tuple, but unknown additional args
C = type("C", (), *args, **kwargs)
reveal_type(C) # revealed: type[Unknown]
# TODO: `type[Unknown]` would cause fewer false positives
reveal_type(C) # revealed: type
# All three positional args provided, only **kwargs unknown
D = type("D", (), {}, **kwargs)
reveal_type(D) # revealed: <class 'D'>
# TODO: `type[Unknown]` would cause fewer false positives
reveal_type(D) # revealed: type
# Three starred expressions - we can't know how they expand
a = ("E",)
b = ((),)
c = ({},)
E = type(*a, *b, *c)
reveal_type(E) # revealed: type[Unknown]
# TODO: `type[Unknown]` would cause fewer false positives
reveal_type(E) # revealed: type
```
## Explicit type annotations
When an explicit type annotation is provided, the inferred type is checked against it:
TODO: Annotated assignments with `type()` calls don't currently synthesize the specific class type.
This will be fixed when we support all `type()` calls (including inline) via generic handling.
```py
# The annotation `type` is compatible with the inferred class literal type
T: type = type("T", (), {})
reveal_type(T) # revealed: <class 'T'>
# The annotation `type[Base]` is compatible with the inferred type
class Base: ...
# TODO: Should infer `<class 'T'>` instead of `type`
T: type = type("T", (), {})
reveal_type(T) # revealed: type
# TODO: Should infer `<class 'Derived'>` instead of `type[Base]}
# error: [invalid-assignment] "Object of type `type` is not assignable to `type[Base]`"
Derived: type[Base] = type("Derived", (Base,), {})
reveal_type(Derived) # revealed: <class 'Derived'>
# Incompatible annotation produces an error
class Unrelated: ...
# error: [invalid-assignment]
Bad: type[Unrelated] = type("Bad", (Base,), {})
reveal_type(Derived) # revealed: type[Base]
```
## Special base classes
Some special base classes work with dynamic class creation, but special semantics may not be fully
synthesized.
### Invalid special bases
Dynamic classes cannot directly inherit from `Generic`, `Protocol`, or `TypedDict`. These special
forms require class syntax for their semantics to be properly applied:
```py
from typing import Generic, Protocol, TypeVar
from typing_extensions import TypedDict
T = TypeVar("T")
# error: [invalid-base] "Invalid base for class created via `type()`"
GenericClass = type("GenericClass", (Generic[T],), {})
# error: [unsupported-dynamic-base] "Unsupported base for class created via `type()`"
ProtocolClass = type("ProtocolClass", (Protocol,), {})
# error: [invalid-base] "Invalid base for class created via `type()`"
TypedDictClass = type("TypedDictClass", (TypedDict,), {})
```
synthesized:
### Protocol bases
Inheriting from a class that is itself a protocol is valid:
```py
# Protocol bases work - the class is created as a subclass of the protocol
from typing import Protocol
from ty_extensions import reveal_mro
@@ -949,9 +918,8 @@ reveal_type(instance) # revealed: ProtoImpl
### TypedDict bases
Inheriting from a class that is itself a TypedDict is valid:
```py
# TypedDict bases work but TypedDict semantics aren't applied to dynamic subclasses
from typing_extensions import TypedDict
from ty_extensions import reveal_mro
@@ -984,26 +952,26 @@ reveal_mro(Point3D) # revealed: (<class 'Point3D'>, <class 'Point'>, <class 'tu
### Enum bases
Creating a class via `type()` that inherits from any Enum class fails at runtime because `EnumMeta`
expects special attributes in the class dict that `type()` doesn't provide:
```py
# Enum subclassing via type() is not supported - EnumMeta requires special dict handling
# that type() cannot provide. This applies even to empty enums.
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
# Enums with members are final and cannot be subclassed
# error: [subclass-of-final-class]
ExtendedColor = type("ExtendedColor", (Color,), {})
class EmptyEnum(Enum):
pass
# Empty enums fail because EnumMeta requires special dict handling
# error: [invalid-base] "Invalid base for class created via `type()`"
InvalidExtension = type("InvalidExtension", (EmptyEnum,), {})
# TODO: We should emit a diagnostic here - type() cannot create Enum subclasses
ExtendedColor = type("ExtendedColor", (Color,), {})
reveal_type(ExtendedColor) # revealed: <class 'ExtendedColor'>
# Even empty enums fail - EnumMeta requires special dict handling
# TODO: We should emit a diagnostic here too
ValidExtension = type("ValidExtension", (EmptyEnum,), {})
reveal_type(ValidExtension) # revealed: <class 'ValidExtension'>
```
## `__init_subclass__` keyword arguments
@@ -1024,6 +992,9 @@ class Child(Base, required_arg="value"):
# The dynamically assigned attribute has Unknown in its type
reveal_type(Child.config) # revealed: Unknown | str
# Dynamic class creation - keyword arguments are not yet supported
# TODO: This should work: type("DynamicChild", (Base,), {}, required_arg="value")
# error: [no-matching-overload]
DynamicChild = type("DynamicChild", (Base,), {}, required_arg="value")
```
@@ -1063,27 +1034,3 @@ reveal_type(Dynamic) # revealed: <class 'Dynamic'>
# Metaclass attributes are accessible on the class
reveal_type(Dynamic.custom_attr) # revealed: str
```
## `final()` on dynamic classes
Using `final()` as a function (not a decorator) on dynamic classes will not be understood by type
checkers. The class is passed through unchanged, and subclassing will not be prevented:
```py
from typing import final
# error: [ineffective-final]
FinalClass = final(type("FinalClass", (), {}))
reveal_type(FinalClass) # revealed: <class 'FinalClass'>
# Subclassing is allowed because type checkers don't understand `final()` called as a function
class Child(FinalClass): ...
# Same with base classes
class Base: ...
# error: [ineffective-final]
FinalDerived = final(type("FinalDerived", (Base,), {}))
class Child2(FinalDerived): ...
```

View File

@@ -1,78 +0,0 @@
# Unsupported base for dynamic `type()` classes
<!-- snapshot-diagnostics -->
## `@final` class
Classes decorated with `@final` cannot be subclassed:
```py
from typing import final
@final
class FinalClass:
pass
X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
```
## `Generic` base
Dynamic classes created via `type()` cannot inherit from `Generic`:
```py
from typing import Generic, TypeVar
T = TypeVar("T")
X = type("X", (Generic[T],), {}) # error: [invalid-base]
```
## `Protocol` base
Dynamic classes created via `type()` cannot inherit from `Protocol`:
```py
from typing import Protocol
X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
```
## `TypedDict` base
Dynamic classes created via `type()` cannot inherit from `TypedDict` directly. Use
`TypedDict("Name", ...)` instead:
```py
from typing_extensions import TypedDict
X = type("X", (TypedDict,), {}) # error: [invalid-base]
```
## Enum base
Dynamic classes created via `type()` cannot inherit from Enum classes because `EnumMeta` expects
special dict attributes that `type()` doesn't provide:
```py
from enum import Enum
class MyEnum(Enum):
pass
X = type("X", (MyEnum,), {}) # error: [invalid-base]
```
## Enum with members
Enums with members are final and cannot be subclassed at all:
```py
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
X = type("X", (Color,), {}) # error: [subclass-of-final-class]
```

View File

@@ -210,15 +210,12 @@ Narrowing does not occur in the same way if `type` is used to dynamically create
```py
def _(x: str | int):
# The following diagnostic is valid, since the three-argument form of `type`
# can only be called with `str` as the first argument.
# Inline type() calls fall back to regular type overload matching.
# TODO: Once inline type() calls synthesize class types, this should narrow x to Never.
#
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `str | int`"
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
if type(x, (), {}) is str:
# But we synthesize a new class object as the result of a three-argument call to `type`,
# and we know that this synthesized class object is not the same object as the `str` class object,
# so here the type is narrowed to `Never`!
reveal_type(x) # revealed: Never
reveal_type(x) # revealed: str | int
else:
reveal_type(x) # revealed: str | int
```

View File

@@ -25,7 +25,6 @@ error[no-matching-overload]: No overload of class `type` matches arguments
1 | type() # error: [no-matching-overload]
| ^^^^^^
|
help: `builtins.type()` can either be called with one or three positional arguments (got 0)
info: rule `no-matching-overload` is enabled by default
```

View File

@@ -1,34 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: type.md - Calls to `type()` - MRO error highlighting (snapshot)
mdtest path: crates/ty_python_semantic/resources/mdtest/call/type.md
---
# Python source files
## mdtest_snippet.py
```
1 | class A: ...
2 |
3 | Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
```
# Diagnostics
```
error[duplicate-base]: Duplicate base class <class 'A'> in class `Dup`
--> src/mdtest_snippet.py:3:7
|
1 | class A: ...
2 |
3 | Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `duplicate-base` is enabled by default
```

View File

@@ -1,39 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - Enum base
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from enum import Enum
2 |
3 | class MyEnum(Enum):
4 | pass
5 |
6 | X = type("X", (MyEnum,), {}) # error: [invalid-base]
```
# Diagnostics
```
error[invalid-base]: Invalid base for class created via `type()`
--> src/mdtest_snippet.py:6:16
|
4 | pass
5 |
6 | X = type("X", (MyEnum,), {}) # error: [invalid-base]
| ^^^^^^ Has type `<class 'MyEnum'>`
|
info: Creating an enum class via `type()` is not supported
info: Consider using `Enum("X", [])` instead
info: rule `invalid-base` is enabled by default
```

View File

@@ -1,38 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - Enum with members
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from enum import Enum
2 |
3 | class Color(Enum):
4 | RED = 1
5 | GREEN = 2
6 |
7 | X = type("X", (Color,), {}) # error: [subclass-of-final-class]
```
# Diagnostics
```
error[subclass-of-final-class]: Class `X` cannot inherit from final class `Color`
--> src/mdtest_snippet.py:7:16
|
5 | GREEN = 2
6 |
7 | X = type("X", (Color,), {}) # error: [subclass-of-final-class]
| ^^^^^
|
info: rule `subclass-of-final-class` is enabled by default
```

View File

@@ -1,38 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `@final` class
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import final
2 |
3 | @final
4 | class FinalClass:
5 | pass
6 |
7 | X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
```
# Diagnostics
```
error[subclass-of-final-class]: Class `X` cannot inherit from final class `FinalClass`
--> src/mdtest_snippet.py:7:16
|
5 | pass
6 |
7 | X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
| ^^^^^^^^^^
|
info: rule `subclass-of-final-class` is enabled by default
```

View File

@@ -1,38 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `Generic` base
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import Generic, TypeVar
2 |
3 | T = TypeVar("T")
4 |
5 | X = type("X", (Generic[T],), {}) # error: [invalid-base]
```
# Diagnostics
```
error[invalid-base]: Invalid base for class created via `type()`
--> src/mdtest_snippet.py:5:16
|
3 | T = TypeVar("T")
4 |
5 | X = type("X", (Generic[T],), {}) # error: [invalid-base]
| ^^^^^^^^^^ Has type `<special-form 'typing.Generic[T]'>`
|
info: Classes created via `type()` cannot be generic
info: Consider using `class X(Generic[...]): ...` instead
info: rule `invalid-base` is enabled by default
```

View File

@@ -1,36 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `Protocol` base
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import Protocol
2 |
3 | X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
```
# Diagnostics
```
info[unsupported-dynamic-base]: Unsupported base for class created via `type()`
--> src/mdtest_snippet.py:3:16
|
1 | from typing import Protocol
2 |
3 | X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
| ^^^^^^^^ Has type `<special-form 'typing.Protocol'>`
|
info: Classes created via `type()` cannot be protocols
info: Consider using `class X(Protocol): ...` instead
info: rule `unsupported-dynamic-base` is enabled by default
```

View File

@@ -1,36 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `TypedDict` base
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import TypedDict
2 |
3 | X = type("X", (TypedDict,), {}) # error: [invalid-base]
```
# Diagnostics
```
error[invalid-base]: Invalid base for class created via `type()`
--> src/mdtest_snippet.py:3:16
|
1 | from typing_extensions import TypedDict
2 |
3 | X = type("X", (TypedDict,), {}) # error: [invalid-base]
| ^^^^^^^^^ Has type `<special-form 'typing.TypedDict'>`
|
info: Classes created via `type()` cannot be TypedDicts
info: Consider using `TypedDict("X", {})` instead
info: rule `invalid-base` is enabled by default
```

View File

@@ -0,0 +1,43 @@
# Subscripts involving type aliases
Aliases are expanded during analysis of subscripts.
```toml
[environment]
python-version = "3.12"
```
```py
from typing_extensions import TypeAlias, Literal
ImplicitTuple = tuple[str, int, int]
PEP613Tuple: TypeAlias = tuple[str, int, int]
type PEP695Tuple = tuple[str, int, int]
ImplicitZero = Literal[0]
PEP613Zero: TypeAlias = Literal[0]
type PEP695Zero = Literal[0]
def f(
implicit_tuple: ImplicitTuple,
pep_613_tuple: PEP613Tuple,
pep_695_tuple: PEP695Tuple,
implicit_zero: ImplicitZero,
pep_613_zero: PEP613Zero,
pep_695_zero: PEP695Zero,
):
reveal_type(implicit_tuple[:2]) # revealed: tuple[str, int]
reveal_type(implicit_tuple[implicit_zero]) # revealed: str
reveal_type(implicit_tuple[pep_613_zero]) # revealed: str
reveal_type(implicit_tuple[pep_695_zero]) # revealed: str
reveal_type(pep_613_tuple[:2]) # revealed: tuple[str, int]
reveal_type(pep_613_tuple[implicit_zero]) # revealed: str
reveal_type(pep_613_tuple[pep_613_zero]) # revealed: str
reveal_type(pep_613_tuple[pep_695_zero]) # revealed: str
reveal_type(pep_695_tuple[:2]) # revealed: tuple[str, int]
reveal_type(pep_695_tuple[implicit_zero]) # revealed: str
reveal_type(pep_695_tuple[pep_613_zero]) # revealed: str
reveal_type(pep_695_tuple[pep_695_zero]) # revealed: str
```

View File

@@ -106,5 +106,5 @@ class Bar:
def f(x: Foo):
if isinstance(x, Bar):
# TODO: should be `int`
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions on intersections)
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions with intersections)
```

View File

@@ -80,6 +80,17 @@ def _(m: int, n: int, s2: str):
reveal_type(substring2) # revealed: str
```
## LiteralString
```py
from typing_extensions import LiteralString
def f(x: LiteralString):
reveal_type(x[0]) # revealed: LiteralString
reveal_type(x[True]) # revealed: LiteralString
reveal_type(x[1:42]) # revealed: LiteralString
```
## Unsupported slice types
```py

View File

@@ -430,5 +430,5 @@ class Bar: ...
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
# TODO: should be `Foo & Bar`
reveal_type(val[0]) # revealed: @Todo(Subscript expressions on intersections)
reveal_type(val[0]) # revealed: @Todo(Subscript expressions with intersections)
```

View File

@@ -0,0 +1,89 @@
# Subscripts involving type variables
## TypeVar bound/constrained to a tuple/int-literal/bool-literal
The upper bounds of type variables are considered when analysing subscripts.
```toml
[environment]
python-version = "3.12"
```
```py
from typing_extensions import TypeAlias, Literal
ImplicitTuple = tuple[str, int, int]
PEP613Tuple: TypeAlias = tuple[str, int, int]
type PEP695Tuple = tuple[str, int, int]
ImplicitZero = Literal[0]
PEP613Zero: TypeAlias = Literal[0]
type PEP695Zero = Literal[0]
# fmt: off
def f[
BoundedTupleT: tuple[str, int, bytes],
ConstrainedTupleT: (tuple[str, int, bytes], tuple[int, bytes, str]),
BoundedZeroT: Literal[0],
ConstrainedIntLiteralT: (Literal[0], Literal[1])
](
tuple_1: BoundedTupleT,
tuple_2: ConstrainedTupleT,
zero: BoundedZeroT,
some_integer: ConstrainedIntLiteralT,
):
# TODO: would ideally be `tuple[str, int]`
reveal_type(tuple_1[:2]) # revealed: tuple[str | int | bytes, ...]
reveal_type(tuple_1[zero]) # revealed: str
# TODO: ideally this might be `str | int`,
# but it's hard to do that without introducing false positives elsewhere
reveal_type(tuple_1[some_integer]) # revealed: str | int | bytes
# TODO: would ideally be `tuple[str, int] | tuple[int, bytes]`
reveal_type(tuple_2[:2]) # revealed: tuple[str | int | bytes, ...]
reveal_type(tuple_2[zero]) # revealed: str | int
reveal_type(tuple_2[some_integer]) # revealed: str | int | bytes
# fmt: on
```
## TypeVars
```toml
[environment]
python-version = "3.12"
```
```py
from typing import Protocol
class SupportsLessThan(Protocol):
def __lt__(self, other, /) -> bool: ...
def f[K: SupportsLessThan](dictionary: dict[K, int], key: K):
reveal_type(dictionary[key]) # revealed: int
```
## ParamSpecs
```toml
[environment]
python-version = "3.12"
```
```py
from typing import Callable
def decorator[**P, T](func: Callable[P, T]) -> Callable[P, T]:
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
if len(args) > 0:
# error: [invalid-assignment]
args = args[1:]
# `func` requires the full `ParamSpec` passed into `decorator`,
# but here the first argument is skipped, so we should possibly emit an error here:
return func(*args, **kwargs)
return inner
```

View File

@@ -938,18 +938,6 @@ impl DefinitionKind<'_> {
| DefinitionKind::ExceptHandler(_) => DefinitionCategory::Binding,
}
}
/// Returns the value expression for assignment-based definitions.
///
/// Returns `Some` for `Assignment` and `AnnotatedAssignment` (if it has a value),
/// `None` for all other definition kinds.
pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> Option<&'ast ast::Expr> {
match self {
DefinitionKind::Assignment(assignment) => Some(assignment.value(module)),
DefinitionKind::AnnotatedAssignment(assignment) => assignment.value(module),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Hash, get_size2::GetSize)]

View File

@@ -2,7 +2,7 @@ use std::ops::Range;
use ruff_db::{files::File, parsed::ParsedModuleRef};
use ruff_index::newtype_index;
use ruff_python_ast::{self as ast, NodeIndex};
use ruff_python_ast as ast;
use crate::{
Db,
@@ -463,27 +463,6 @@ impl NodeWithScopeKind {
_ => None,
}
}
/// Returns the anchor node index for this scope, or `None` for the module scope.
///
/// This is used to compute relative node indices for expressions within the scope,
/// providing a stable anchor that only changes when the scope-introducing node changes.
pub(crate) fn node_index(&self) -> Option<NodeIndex> {
match self {
Self::Module => None,
Self::Class(class) => Some(class.index()),
Self::ClassTypeParameters(class) => Some(class.index()),
Self::Function(function) => Some(function.index()),
Self::FunctionTypeParameters(function) => Some(function.index()),
Self::TypeAlias(type_alias) => Some(type_alias.index()),
Self::TypeAliasTypeParameters(type_alias) => Some(type_alias.index()),
Self::Lambda(lambda) => Some(lambda.index()),
Self::ListComprehension(comp) => Some(comp.index()),
Self::SetComprehension(comp) => Some(comp.index()),
Self::DictComprehension(comp) => Some(comp.index()),
Self::GeneratorExpression(generator) => Some(generator.index()),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]

View File

@@ -4111,6 +4111,55 @@ impl<'db> Type<'db> {
.into()
}
Some(KnownClass::Type) => {
let str_instance = KnownClass::Str.to_instance(db);
let type_instance = KnownClass::Type.to_instance(db);
// ```py
// class type:
// @overload
// def __init__(self, o: object, /) -> None: ...
// @overload
// def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ...
// ```
CallableBinding::from_overloads(
self,
[
Signature::new(
Parameters::new(
db,
[Parameter::positional_only(Some(Name::new_static("o")))
.with_annotated_type(Type::any())],
),
type_instance,
),
Signature::new(
Parameters::new(
db,
[
Parameter::positional_only(Some(Name::new_static("name")))
.with_annotated_type(str_instance),
Parameter::positional_only(Some(Name::new_static("bases")))
.with_annotated_type(Type::homogeneous_tuple(
db,
type_instance,
)),
Parameter::positional_only(Some(Name::new_static("dict")))
.with_annotated_type(
KnownClass::Dict.to_specialized_instance(
db,
&[str_instance, Type::any()],
),
),
],
),
type_instance,
),
],
)
.into()
}
Some(KnownClass::Object) => {
// ```py
// class object:
@@ -6510,9 +6559,9 @@ impl<'db> Type<'db> {
Some(TypeDefinition::Function(function.definition(db)))
}
Self::ModuleLiteral(module) => Some(TypeDefinition::Module(module.module(db))),
Self::ClassLiteral(class_literal) => class_literal.type_definition(db),
Self::ClassLiteral(class_literal) => Some(class_literal.type_definition(db)),
Self::GenericAlias(alias) => Some(TypeDefinition::StaticClass(alias.definition(db))),
Self::NominalInstance(instance) => instance.class(db).type_definition(db),
Self::NominalInstance(instance) => Some(instance.class(db).type_definition(db)),
Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => {
Some(TypeDefinition::TypeVar(var.definition(db)?))
@@ -6526,7 +6575,7 @@ impl<'db> Type<'db> {
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
SubclassOfInner::Dynamic(_) => None,
SubclassOfInner::Class(class) => class.type_definition(db),
SubclassOfInner::Class(class) => Some(class.type_definition(db)),
SubclassOfInner::TypeVar(bound_typevar) => {
Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?))
}
@@ -6556,7 +6605,7 @@ impl<'db> Type<'db> {
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::ProtocolInstance(protocol) => match protocol.inner {
Protocol::FromClass(class) => class.type_definition(db),
Protocol::FromClass(class) => Some(class.type_definition(db)),
Protocol::Synthesized(_) => None,
},

View File

@@ -1443,6 +1443,12 @@ impl<'db> Bindings<'db> {
}
}
Some(KnownClass::Type) if overload_index == 0 => {
if let [Some(arg)] = overload.parameter_types() {
overload.set_return_type(arg.dunder_class(db));
}
}
Some(KnownClass::Property) => {
if let [getter, setter, ..] = overload.parameter_types() {
overload.set_return_type(Type::PropertyInstance(

View File

@@ -60,7 +60,7 @@ use crate::{
attribute_assignments,
definition::{DefinitionKind, TargetKind},
place_table,
scope::ScopeId,
scope::{FileScopeId, ScopeId},
semantic_index, use_def_map,
},
types::{
@@ -74,7 +74,7 @@ use ruff_db::diagnostic::Span;
use ruff_db::files::File;
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, NodeIndex, PythonVersion};
use ruff_python_ast::{self as ast, PythonVersion};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
use ty_module_resolver::{KnownModule, file_to_module};
@@ -562,16 +562,25 @@ impl<'db> ClassLiteral<'db> {
}
/// Returns the generic context if this is a generic class.
///
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
// to inherit from `Generic[T]`, since dynamic classes can't be generic. See also: `is_protocol`.
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
self.as_static().and_then(|class| class.generic_context(db))
}
/// Returns whether this class is a protocol.
///
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
// to inherit from `Protocol`, since dynamic classes can't be protocols. See also: `generic_context`.
pub(crate) fn is_protocol(self, db: &'db dyn Db) -> bool {
self.as_static().is_some_and(|class| class.is_protocol(db))
}
/// Returns whether this class is a `TypedDict`.
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
// to inherit from `TypedDict`. To create a dynamic TypedDict, you should invoke
// `TypedDict` as a function, not `type`. See also: `generic_context`, `is_protocol`.
pub fn is_typed_dict(self, db: &'db dyn Db) -> bool {
match self {
Self::Static(class) => class.is_typed_dict(db),
@@ -604,7 +613,7 @@ impl<'db> ClassLiteral<'db> {
pub(crate) fn file(self, db: &dyn Db) -> File {
match self {
Self::Static(class) => class.file(db),
Self::Dynamic(class) => class.scope(db).file(db),
Self::Dynamic(class) => class.file(db),
}
}
@@ -625,12 +634,10 @@ impl<'db> ClassLiteral<'db> {
}
/// Returns whether this class is final.
// TODO: Support `@final` on dynamic classes, e.g. `X = final(type("X", (), {}))`.
// We should either recognize and track this, or emit a diagnostic if unsupported.
pub(crate) fn is_final(self, db: &'db dyn Db) -> bool {
match self {
Self::Static(class) => class.is_final(db),
// Dynamic classes created via `type()` cannot be marked as final.
Self::Dynamic(_) => false,
}
self.as_static().is_some_and(|class| class.is_final(db))
}
/// Returns `true` if this class defines any ordering method (`__lt__`, `__le__`, `__gt__`,
@@ -657,10 +664,10 @@ impl<'db> ClassLiteral<'db> {
}
}
/// Returns the definition of this class, if available.
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
/// Returns the definition of this class.
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
match self {
Self::Static(class) => Some(class.definition(db)),
Self::Static(class) => class.definition(db),
Self::Dynamic(class) => class.definition(db),
}
}
@@ -668,11 +675,11 @@ impl<'db> ClassLiteral<'db> {
/// Returns the type definition for this class.
///
/// For static classes, returns `TypeDefinition::StaticClass`.
/// For dynamic classes, returns `TypeDefinition::DynamicClass` if a definition is available.
pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
/// For dynamic classes, returns `TypeDefinition::DynamicClass`.
pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
match self {
Self::Static(class) => Some(TypeDefinition::StaticClass(class.definition(db))),
Self::Dynamic(class) => class.definition(db).map(TypeDefinition::DynamicClass),
Self::Static(class) => TypeDefinition::StaticClass(class.definition(db)),
Self::Dynamic(class) => TypeDefinition::DynamicClass(class.definition(db)),
}
}
@@ -937,13 +944,13 @@ impl<'db> ClassType<'db> {
self.class_literal(db).known(db)
}
/// Returns the definition for this class, if available.
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
/// Returns the definition for this class.
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.class_literal(db).definition(db)
}
/// Returns the type definition for this class.
pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
self.class_literal(db).type_definition(db)
}
@@ -4697,8 +4704,12 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
///
/// # Salsa interning
///
/// This is a Salsa-interned struct. Two different `type()` calls always produce
/// distinct `DynamicClassLiteral` instances, even if they have the same name and bases:
/// Each `type()` call is uniquely identified by its [`Definition`], which provides
/// stable identity without depending on AST node indices that can change when code
/// is inserted above the call site.
///
/// Two different `type()` calls always produce distinct `DynamicClassLiteral`
/// instances, even if they have the same name and bases:
///
/// ```python
/// Foo1 = type("Foo", (Base,), {})
@@ -4706,11 +4717,9 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
/// # Foo1 and Foo2 are distinct types
/// ```
///
/// The `anchor` field provides stable identity:
/// - For assigned `type()` calls, the `Definition` uniquely identifies the class.
/// - For dangling `type()` calls, a relative node offset anchored to the enclosing scope
/// provides stable identity that only changes when the scope itself changes.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
/// Note: Only assigned `type()` calls are currently supported (e.g., `Foo = type(...)`).
/// Inline calls like `process(type(...))` fall back to normal call handling.
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DynamicClassLiteral<'db> {
/// The name of the class (from the first argument to `type()`).
@@ -4721,13 +4730,8 @@ pub struct DynamicClassLiteral<'db> {
#[returns(deref)]
pub bases: Box<[ClassBase<'db>]>,
/// The anchor for this dynamic class, providing stable identity.
///
/// - `Definition`: The `type()` call is assigned to a variable. The definition
/// uniquely identifies this class and can be used to find the `type()` call.
/// - `ScopeOffset`: The `type()` call is "dangling" (not assigned). The offset
/// is relative to the enclosing scope's anchor node index.
pub anchor: DynamicClassAnchor<'db>,
/// The definition where this class is created.
pub definition: Definition<'db>,
/// The class members from the namespace dict (third argument to `type()`).
/// Each entry is a (name, type) pair extracted from the dict literal.
@@ -4744,87 +4748,38 @@ pub struct DynamicClassLiteral<'db> {
pub dataclass_params: Option<DataclassParams<'db>>,
}
/// Anchor for identifying a dynamic class literal.
///
/// This enum provides stable identity for `DynamicClassLiteral`:
/// - For assigned calls, the `Definition` uniquely identifies the class.
/// - For dangling calls, a relative offset provides stable identity.
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, salsa::Update, get_size2::GetSize,
)]
pub enum DynamicClassAnchor<'db> {
/// The `type()` call is assigned to a variable.
///
/// The `Definition` uniquely identifies this class. The `type()` call expression
/// is the `value` of the assignment, so we can get its range from the definition.
Definition(Definition<'db>),
/// The `type()` call is "dangling" (not assigned to a variable).
///
/// The offset is relative to the enclosing scope's anchor node index.
/// For module scope, this is equivalent to an absolute index (anchor is 0).
ScopeOffset { scope: ScopeId<'db>, offset: u32 },
}
impl get_size2::GetSize for DynamicClassLiteral<'_> {}
#[salsa::tracked]
impl<'db> DynamicClassLiteral<'db> {
/// Returns the definition where this class is created, if it was assigned to a variable.
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
match self.anchor(db) {
DynamicClassAnchor::Definition(definition) => Some(definition),
DynamicClassAnchor::ScopeOffset { .. } => None,
}
}
/// Returns the scope in which this dynamic class was created.
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
match self.anchor(db) {
DynamicClassAnchor::Definition(definition) => definition.scope(db),
DynamicClassAnchor::ScopeOffset { scope, .. } => scope,
}
}
/// Returns a [`Span`] with the range of the `type()` call expression.
///
/// See [`Self::header_range`] for more details.
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
Span::from(self.scope(db).file(db)).with_range(self.header_range(db))
Span::from(self.file(db)).with_range(self.header_range(db))
}
/// Returns the range of the `type()` call expression that created this class.
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
let scope = self.scope(db);
let file = scope.file(db);
let definition = self.definition(db);
let file = definition.file(db);
let module = parsed_module(db, file).load(db);
match self.anchor(db) {
DynamicClassAnchor::Definition(definition) => {
// For definitions, get the range from the definition's value.
// The `type()` call is the value of the assignment.
definition
.kind(db)
.value(&module)
.expect("DynamicClassAnchor::Definition should only be used for assignments")
.range()
}
DynamicClassAnchor::ScopeOffset { offset, .. } => {
// For dangling `type()` calls, compute the absolute index from the offset.
let scope_anchor = scope.node(db).node_index().unwrap_or(NodeIndex::from(0));
let anchor_u32 = scope_anchor
.as_u32()
.expect("anchor should not be NodeIndex::NONE");
let absolute_index = NodeIndex::from(anchor_u32 + offset);
// Dynamic classes are only created from regular assignments (e.g., `Foo = type(...)`).
let DefinitionKind::Assignment(assignment) = definition.kind(db) else {
unreachable!("DynamicClassLiteral should only be created from Assignment definitions");
};
assignment.value(&module).range()
}
// Get the node and return its range.
let node: &ast::ExprCall = module
.get_by_index(absolute_index)
.try_into()
.expect("scope offset should point to ExprCall");
node.range()
}
}
/// Returns the file containing the `type()` call.
pub(crate) fn file(self, db: &'db dyn Db) -> File {
self.definition(db).file(db)
}
/// Returns the scope containing the `type()` call.
pub(crate) fn file_scope(self, db: &'db dyn Db) -> FileScopeId {
self.definition(db).file_scope(db)
}
/// Get the metaclass of this dynamic class.
@@ -5065,7 +5020,7 @@ impl<'db> DynamicClassLiteral<'db> {
db,
self.name(db).clone(),
self.bases(db),
self.anchor(db),
self.definition(db),
self.members(db),
self.has_dynamic_namespace(db),
dataclass_params,
@@ -5359,8 +5314,7 @@ impl<'db> QualifiedClassName<'db> {
}
ClassLiteral::Dynamic(class) => {
// Dynamic classes don't have a body scope; start from the enclosing scope.
let scope = class.scope(self.db);
(scope.file(self.db), scope.file_scope_id(self.db), 0)
(class.file(self.db), class.file_scope(self.db), 0)
}
};

View File

@@ -1004,8 +1004,9 @@ impl<'db> Node<'db> {
Node::AlwaysTrue => {}
Node::AlwaysFalse => {}
Node::Interior(interior) => {
let mut path = interior.path_assignments(db);
self.for_each_path_inner(db, &mut f, &mut path);
let map = interior.sequent_map(db);
let mut path = PathAssignments::default();
self.for_each_path_inner(db, &mut f, map, &mut path);
}
}
}
@@ -1014,6 +1015,7 @@ impl<'db> Node<'db> {
self,
db: &'db dyn Db,
f: &mut dyn FnMut(&PathAssignments<'db>),
map: &SequentMap<'db>,
path: &mut PathAssignments<'db>,
) {
match self {
@@ -1022,11 +1024,11 @@ impl<'db> Node<'db> {
Node::Interior(interior) => {
let constraint = interior.constraint(db);
let source_order = interior.source_order(db);
path.walk_edge(db, constraint.when_true(), source_order, |path, _| {
interior.if_true(db).for_each_path_inner(db, f, path);
path.walk_edge(db, map, constraint.when_true(), source_order, |path, _| {
interior.if_true(db).for_each_path_inner(db, f, map, path);
});
path.walk_edge(db, constraint.when_false(), source_order, |path, _| {
interior.if_false(db).for_each_path_inner(db, f, path);
path.walk_edge(db, map, constraint.when_false(), source_order, |path, _| {
interior.if_false(db).for_each_path_inner(db, f, map, path);
});
}
}
@@ -1038,13 +1040,19 @@ impl<'db> Node<'db> {
Node::AlwaysTrue => true,
Node::AlwaysFalse => false,
Node::Interior(interior) => {
let mut path = interior.path_assignments(db);
self.is_always_satisfied_inner(db, &mut path)
let map = interior.sequent_map(db);
let mut path = PathAssignments::default();
self.is_always_satisfied_inner(db, map, &mut path)
}
}
}
fn is_always_satisfied_inner(self, db: &'db dyn Db, path: &mut PathAssignments<'db>) -> bool {
fn is_always_satisfied_inner(
self,
db: &'db dyn Db,
map: &SequentMap<'db>,
path: &mut PathAssignments<'db>,
) -> bool {
match self {
Node::AlwaysTrue => true,
Node::AlwaysFalse => false,
@@ -1055,8 +1063,10 @@ impl<'db> Node<'db> {
let constraint = interior.constraint(db);
let source_order = interior.source_order(db);
let true_always_satisfied = path
.walk_edge(db, constraint.when_true(), source_order, |path, _| {
interior.if_true(db).is_always_satisfied_inner(db, path)
.walk_edge(db, map, constraint.when_true(), source_order, |path, _| {
interior
.if_true(db)
.is_always_satisfied_inner(db, map, path)
})
.unwrap_or(true);
if !true_always_satisfied {
@@ -1064,8 +1074,10 @@ impl<'db> Node<'db> {
}
// Ditto for the if_false branch
path.walk_edge(db, constraint.when_false(), source_order, |path, _| {
interior.if_false(db).is_always_satisfied_inner(db, path)
path.walk_edge(db, map, constraint.when_false(), source_order, |path, _| {
interior
.if_false(db)
.is_always_satisfied_inner(db, map, path)
})
.unwrap_or(true)
}
@@ -1078,13 +1090,19 @@ impl<'db> Node<'db> {
Node::AlwaysTrue => false,
Node::AlwaysFalse => true,
Node::Interior(interior) => {
let mut path = interior.path_assignments(db);
self.is_never_satisfied_inner(db, &mut path)
let map = interior.sequent_map(db);
let mut path = PathAssignments::default();
self.is_never_satisfied_inner(db, map, &mut path)
}
}
}
fn is_never_satisfied_inner(self, db: &'db dyn Db, path: &mut PathAssignments<'db>) -> bool {
fn is_never_satisfied_inner(
self,
db: &'db dyn Db,
map: &SequentMap<'db>,
path: &mut PathAssignments<'db>,
) -> bool {
match self {
Node::AlwaysTrue => false,
Node::AlwaysFalse => true,
@@ -1095,8 +1113,8 @@ impl<'db> Node<'db> {
let constraint = interior.constraint(db);
let source_order = interior.source_order(db);
let true_never_satisfied = path
.walk_edge(db, constraint.when_true(), source_order, |path, _| {
interior.if_true(db).is_never_satisfied_inner(db, path)
.walk_edge(db, map, constraint.when_true(), source_order, |path, _| {
interior.if_true(db).is_never_satisfied_inner(db, map, path)
})
.unwrap_or(true);
if !true_never_satisfied {
@@ -1104,8 +1122,10 @@ impl<'db> Node<'db> {
}
// Ditto for the if_false branch
path.walk_edge(db, constraint.when_false(), source_order, |path, _| {
interior.if_false(db).is_never_satisfied_inner(db, path)
path.walk_edge(db, map, constraint.when_false(), source_order, |path, _| {
interior
.if_false(db)
.is_never_satisfied_inner(db, map, path)
})
.unwrap_or(true)
}
@@ -1391,12 +1411,13 @@ impl<'db> Node<'db> {
self,
db: &'db dyn Db,
should_remove: &mut dyn FnMut(ConstrainedTypeVar<'db>) -> bool,
map: &SequentMap<'db>,
path: &mut PathAssignments<'db>,
) -> Self {
match self {
Node::AlwaysTrue => Node::AlwaysTrue,
Node::AlwaysFalse => Node::AlwaysFalse,
Node::Interior(interior) => interior.abstract_one_inner(db, should_remove, path),
Node::Interior(interior) => interior.abstract_one_inner(db, should_remove, map, path),
}
}
@@ -1986,7 +2007,8 @@ impl<'db> InteriorNode<'db> {
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Node<'db> {
let mut path = self.path_assignments(db);
let map = self.sequent_map(db);
let mut path = PathAssignments::default();
let mentions_typevar = |ty: Type<'db>| match ty {
Type::TypeVar(haystack) => haystack.identity(db) == bound_typevar,
_ => false,
@@ -2012,13 +2034,15 @@ impl<'db> InteriorNode<'db> {
}
false
},
map,
&mut path,
)
}
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn retain_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Node<'db> {
let mut path = self.path_assignments(db);
let map = self.sequent_map(db);
let mut path = PathAssignments::default();
self.abstract_one_inner(
db,
// Remove any node that constrains some other typevar than `bound_typevar`, and any
@@ -2034,6 +2058,7 @@ impl<'db> InteriorNode<'db> {
}
false
},
map,
&mut path,
)
}
@@ -2042,6 +2067,7 @@ impl<'db> InteriorNode<'db> {
self,
db: &'db dyn Db,
should_remove: &mut dyn FnMut(ConstrainedTypeVar<'db>) -> bool,
map: &SequentMap<'db>,
path: &mut PathAssignments<'db>,
) -> Node<'db> {
let self_constraint = self.constraint(db);
@@ -2063,10 +2089,13 @@ impl<'db> InteriorNode<'db> {
let if_true = path
.walk_edge(
db,
map,
self_constraint.when_true(),
self_source_order,
|path, new_range| {
let branch = self.if_true(db).abstract_one_inner(db, should_remove, path);
let branch =
self.if_true(db)
.abstract_one_inner(db, should_remove, map, path);
path.assignments[new_range]
.iter()
.filter(|(assignment, _)| {
@@ -2086,12 +2115,13 @@ impl<'db> InteriorNode<'db> {
let if_false = path
.walk_edge(
db,
map,
self_constraint.when_false(),
self_source_order,
|path, new_range| {
let branch = self
.if_false(db)
.abstract_one_inner(db, should_remove, path);
let branch =
self.if_false(db)
.abstract_one_inner(db, should_remove, map, path);
path.assignments[new_range]
.iter()
.filter(|(assignment, _)| {
@@ -2114,19 +2144,24 @@ impl<'db> InteriorNode<'db> {
let if_true = path
.walk_edge(
db,
map,
self_constraint.when_true(),
self_source_order,
|path, _| self.if_true(db).abstract_one_inner(db, should_remove, path),
|path, _| {
self.if_true(db)
.abstract_one_inner(db, should_remove, map, path)
},
)
.unwrap_or(Node::AlwaysFalse);
let if_false = path
.walk_edge(
db,
map,
self_constraint.when_false(),
self_source_order,
|path, _| {
self.if_false(db)
.abstract_one_inner(db, should_remove, path)
.abstract_one_inner(db, should_remove, map, path)
},
)
.unwrap_or(Node::AlwaysFalse);
@@ -2198,14 +2233,11 @@ impl<'db> InteriorNode<'db> {
});
constraints.sort_unstable_by_key(|(_, source_order)| *source_order);
SequentMap::new(constraints.into_iter().map(|(constraint, _)| constraint))
}
fn path_assignments(self, db: &'db dyn Db) -> PathAssignments<'db> {
PathAssignments {
map: self.sequent_map(db).clone(),
assignments: FxOrderMap::default(),
let mut map = SequentMap::default();
for (constraint, _) in constraints {
map.add(db, constraint);
}
map
}
/// Returns a simplified version of a BDD.
@@ -2751,7 +2783,7 @@ impl<'db> ConstraintAssignment<'db> {
///
/// - `C → D`: This indicates that `C` on its own is enough to imply `D`. Any path that assumes `C`
/// holds but `D` does _not_ is impossible and can be pruned.
#[derive(Clone, Debug, Default, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
#[derive(Debug, Default, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
struct SequentMap<'db> {
/// Sequents of the form `¬C₁ → false`
single_tautologies: FxHashSet<ConstrainedTypeVar<'db>>,
@@ -2764,67 +2796,54 @@ struct SequentMap<'db> {
>,
/// Sequents of the form `C → D`
single_implications: FxHashMap<ConstrainedTypeVar<'db>, FxOrderSet<ConstrainedTypeVar<'db>>>,
/// Constraints that we have discovered, mapped to whether we have processed them yet. (This
/// ensures a stable order for all of the derived constraints that we create, while still
/// letting us create them lazily.)
discovered: FxOrderMap<ConstrainedTypeVar<'db>, bool>,
/// Constraints that we have already processed
processed: FxHashSet<ConstrainedTypeVar<'db>>,
/// Constraints that enqueued to be processed
enqueued: Vec<ConstrainedTypeVar<'db>>,
}
impl<'db> SequentMap<'db> {
fn new(constraints: impl IntoIterator<Item = ConstrainedTypeVar<'db>>) -> Self {
let discovered = constraints
.into_iter()
.map(|constraint| (constraint, false))
.collect();
Self {
single_tautologies: FxHashSet::default(),
pair_impossibilities: FxHashSet::default(),
pair_implications: FxHashMap::default(),
single_implications: FxHashMap::default(),
discovered,
fn add(&mut self, db: &'db dyn Db, constraint: ConstrainedTypeVar<'db>) {
self.enqueue_constraint(constraint);
while let Some(constraint) = self.enqueued.pop() {
// If we've already processed this constraint, we can skip it.
if !self.processed.insert(constraint) {
continue;
}
// First see if we can create any sequents from the constraint on its own.
tracing::trace!(
target: "ty_python_semantic::types::constraints::SequentMap",
constraint = %constraint.display(db),
"add sequents for constraint",
);
self.add_sequents_for_single(db, constraint);
// Then check this constraint against all of the other ones we've seen so far, seeing
// if they're related to each other.
let processed = std::mem::take(&mut self.processed);
for other in &processed {
if constraint != *other {
tracing::trace!(
target: "ty_python_semantic::types::constraints::SequentMap",
left = %constraint.display(db),
right = %other.display(db),
"add sequents for constraint pair",
);
self.add_sequents_for_pair(db, constraint, *other);
}
}
self.processed = processed;
}
}
fn add(&mut self, db: &'db dyn Db, constraint: ConstrainedTypeVar<'db>) {
fn enqueue_constraint(&mut self, constraint: ConstrainedTypeVar<'db>) {
// If we've already processed this constraint, we can skip it.
let (new_index, existing) = self.discovered.insert_full(constraint, true);
let already_processed =
existing.expect("should not process constraint before discovering it");
if already_processed {
if self.processed.contains(&constraint) {
return;
}
// First see if we can create any sequents from the constraint on its own.
tracing::trace!(
target: "ty_python_semantic::types::constraints::SequentMap",
constraint = %constraint.display(db),
"add sequents for constraint",
);
self.add_sequents_for_single(db, constraint);
// Then check this constraint against all of the other ones we've seen so far, seeing
// if they're related to each other.
for other_index in 0..self.discovered.len() {
if new_index != other_index {
let other_constraint = self.discovered.keys()[other_index];
let (left, right) = if new_index < other_index {
(constraint, other_constraint)
} else {
(other_constraint, constraint)
};
tracing::trace!(
target: "ty_python_semantic::types::constraints::SequentMap",
left = %left.display(db),
right = %right.display(db),
"add sequents for constraint pair",
);
self.add_sequents_for_pair(db, left, right);
}
}
}
fn discover_constraint(&mut self, constraint: ConstrainedTypeVar<'db>) {
self.discovered.insert(constraint, false);
self.enqueued.push(constraint);
}
fn pair_key(
@@ -2874,7 +2893,6 @@ impl<'db> SequentMap<'db> {
ante2: ConstrainedTypeVar<'db>,
post: ConstrainedTypeVar<'db>,
) {
self.discover_constraint(post);
// If either antecedent implies the consequent on its own, this new sequent is redundant.
if ante1.implies(db, post) || ante2.implies(db, post) {
return;
@@ -2907,7 +2925,6 @@ impl<'db> SequentMap<'db> {
if ante == post {
return;
}
self.discover_constraint(post);
if self
.single_implications
.entry(ante)
@@ -2970,6 +2987,7 @@ impl<'db> SequentMap<'db> {
};
self.add_single_implication(db, constraint, post_constraint);
self.enqueue_constraint(post_constraint);
}
fn add_sequents_for_pair(
@@ -3109,6 +3127,7 @@ impl<'db> SequentMap<'db> {
let post_constraint =
ConstrainedTypeVar::new(db, constrained_typevar, new_lower, new_upper);
self.add_pair_implication(db, left_constraint, right_constraint, post_constraint);
self.enqueue_constraint(post_constraint);
}
fn add_mutual_sequents_for_same_typevars(
@@ -3158,6 +3177,7 @@ impl<'db> SequentMap<'db> {
_ => return,
};
self.add_pair_implication(db, left_constraint, right_constraint, post_constraint);
self.enqueue_constraint(post_constraint);
};
try_one_direction(left_constraint, right_constraint);
@@ -3211,6 +3231,7 @@ impl<'db> SequentMap<'db> {
);
self.add_single_implication(db, intersection_constraint, left_constraint);
self.add_single_implication(db, intersection_constraint, right_constraint);
self.enqueue_constraint(intersection_constraint);
}
// The sequent map only needs to include constraints that might appear in a BDD. If the
@@ -3297,9 +3318,8 @@ impl<'db> SequentMap<'db> {
/// The collection of constraints that we know to be true or false at a certain point when
/// traversing a BDD.
#[derive(Debug)]
#[derive(Debug, Default)]
pub(crate) struct PathAssignments<'db> {
map: SequentMap<'db>,
assignments: FxOrderMap<ConstraintAssignment<'db>, usize>,
}
@@ -3329,6 +3349,7 @@ impl<'db> PathAssignments<'db> {
fn walk_edge<R>(
&mut self,
db: &'db dyn Db,
map: &SequentMap<'db>,
assignment: ConstraintAssignment<'db>,
source_order: usize,
f: impl FnOnce(&mut Self, Range<usize>) -> R,
@@ -3348,7 +3369,7 @@ impl<'db> PathAssignments<'db> {
edge = %assignment.display(db),
"walk edge",
);
let found_conflict = self.add_assignment(db, assignment, source_order);
let found_conflict = self.add_assignment(db, map, assignment, source_order);
let result = if found_conflict.is_err() {
// If that results in the path now being impossible due to a contradiction, return
// without invoking the callback.
@@ -3398,6 +3419,7 @@ impl<'db> PathAssignments<'db> {
fn add_assignment(
&mut self,
db: &'db dyn Db,
map: &SequentMap<'db>,
assignment: ConstraintAssignment<'db>,
source_order: usize,
) -> Result<(), PathAssignmentConflict> {
@@ -3431,9 +3453,7 @@ impl<'db> PathAssignments<'db> {
// don't anticipate the sequent maps to be very large. We might consider avoiding the
// brute-force search.
self.map.add(db, assignment.constraint());
for ante in &self.map.single_tautologies {
for ante in &map.single_tautologies {
if self.assignment_holds(ante.when_false()) {
// The sequent map says (ante1) is always true, and the current path asserts that
// it's false.
@@ -3450,7 +3470,7 @@ impl<'db> PathAssignments<'db> {
}
}
for (ante1, ante2) in &self.map.pair_impossibilities {
for (ante1, ante2) in &map.pair_impossibilities {
if self.assignment_holds(ante1.when_true()) && self.assignment_holds(ante2.when_true())
{
// The sequent map says (ante1 ∧ ante2) is an impossible combination, and the
@@ -3469,29 +3489,24 @@ impl<'db> PathAssignments<'db> {
}
}
let mut new_constraints = Vec::new();
for ((ante1, ante2), posts) in &self.map.pair_implications {
for ((ante1, ante2), posts) in &map.pair_implications {
for post in posts {
if self.assignment_holds(ante1.when_true())
&& self.assignment_holds(ante2.when_true())
{
new_constraints.push(*post);
self.add_assignment(db, map, post.when_true(), source_order)?;
}
}
}
for (ante, posts) in &self.map.single_implications {
for (ante, posts) in &map.single_implications {
for post in posts {
if self.assignment_holds(ante.when_true()) {
new_constraints.push(*post);
self.add_assignment(db, map, post.when_true(), source_order)?;
}
}
}
for new_constraint in new_constraints {
self.add_assignment(db, new_constraint.when_true(), source_order)?;
}
Ok(())
}
}

View File

@@ -104,7 +104,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
registry.register_lint(&OVERRIDE_OF_FINAL_METHOD);
registry.register_lint(&INEFFECTIVE_FINAL);
registry.register_lint(&TYPE_ASSERTION_FAILURE);
registry.register_lint(&TOO_MANY_POSITIONAL_ARGUMENTS);
registry.register_lint(&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS);
@@ -1790,34 +1789,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for calls to `final()` that type checkers cannot interpret.
///
/// ## Why is this bad?
/// The `final()` function is designed to be used as a decorator. When called directly
/// as a function (e.g., `final(type(...))`), type checkers will not understand the
/// application of `final` and will not prevent subclassing.
///
/// ## Example
///
/// ```python
/// from typing import final
///
/// # Incorrect: type checkers will not prevent subclassing
/// MyClass = final(type("MyClass", (), {}))
///
/// # Correct: use `final` as a decorator
/// @final
/// class MyClass: ...
/// ```
pub(crate) static INEFFECTIVE_FINAL = {
summary: "detects calls to `final()` that type checkers cannot interpret",
status: LintStatus::preview("0.0.1-alpha.33"),
default_level: Level::Warn,
}
}
declare_lint! {
/// ## What it does
/// Checks for methods that are decorated with `@override` but do not override any method in a superclass.

View File

@@ -67,6 +67,7 @@ pub(crate) fn enum_metadata<'db>(
//
// MyEnum = type("MyEnum", (BaseEnum,), {"A": 1, "B": 2})
// ```
// TODO: Add a diagnostic for including an enum in a `type(...)` call.
return None;
}
};

View File

@@ -96,7 +96,7 @@ pub(crate) fn typing_self<'db>(
let identity = TypeVarIdentity::new(
db,
ast::name::Name::new_static("Self"),
class.definition(db),
Some(class.definition(db)),
TypeVarKind::TypingSelf,
);
let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance(

View File

@@ -169,9 +169,9 @@ pub fn definitions_for_name<'db>(
// instead of `int` (hover only shows the docstring of the first definition).
.rev()
.filter_map(|ty| ty.as_nominal_instance())
.filter_map(|instance| {
let definition = instance.class_literal(db).definition(db)?;
Some(ResolvedDefinition::Definition(definition))
.map(|instance| {
let definition = instance.class_literal(db).definition(db);
ResolvedDefinition::Definition(definition)
})
.collect();
}

View File

@@ -55,22 +55,21 @@ use crate::subscript::{PyIndex, PySlice};
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
use crate::types::call::{Argument, Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::class::{
ClassLiteral, CodeGeneratorKind, DynamicClassAnchor, DynamicClassLiteral,
DynamicMetaclassConflict, FieldKind, MetaclassErrorKind, MethodDecorator,
ClassLiteral, CodeGeneratorKind, DynamicClassLiteral, DynamicMetaclassConflict, FieldKind,
MetaclassErrorKind, MethodDecorator,
};
use crate::types::context::{InNoTypeCheck, InferContext};
use crate::types::cyclic::CycleDetector;
use crate::types::diagnostic::{
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, CYCLIC_TYPE_ALIAS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE,
DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE,
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE,
INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD,
INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS,
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION,
INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases,
NO_MATCHING_OVERLOAD, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT,
INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_GENERIC_CLASS,
INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM,
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_CONSTRAINTS,
INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR,
@@ -5582,7 +5581,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Try to extract the dynamic class with definition.
// This returns `None` if it's not a three-arg call to `type()`,
// signalling that we must fall back to normal call inference.
self.infer_builtins_type_call(call_expr, Some(definition))
self.infer_dynamic_type_expression(call_expr, definition)
.unwrap_or_else(|| {
self.infer_call_expression_impl(call_expr, callable_type, tcx)
})
}
Some(_) | None => {
self.infer_call_expression_impl(call_expr, callable_type, tcx)
@@ -6187,21 +6189,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
/// Infer a call to `builtins.type()`.
/// Try to infer a 3-argument `type(name, bases, dict)` call expression, capturing the definition.
///
/// `builtins.type` has two overloads: a single-argument overload (e.g. `type("foo")`,
/// and a 3-argument `type(name, bases, dict)` overload. Both are handled here.
/// The `definition` parameter should be `Some()` if this call to `builtins.type()`
/// occurs on the right-hand side of an assignment statement that has a [`Definition`]
/// associated with it in the semantic index.
/// This is called when we detect a `type()` call in assignment context and want to
/// associate the resulting `DynamicClassLiteral` with its definition for go-to-definition.
///
/// If it's unclear which overload we should pick, we return `type[Unknown]`,
/// to avoid cascading errors later on.
fn infer_builtins_type_call(
/// Returns `None` if any keywords were provided or the number of arguments is not three,
/// signalling that no types were stored for any AST sub-expressions and that we should
/// therefore fallback to normal call binding for error reporting.
fn infer_dynamic_type_expression(
&mut self,
call_expr: &ast::ExprCall,
definition: Option<Definition<'db>>,
) -> Type<'db> {
definition: Definition<'db>,
) -> Option<Type<'db>> {
let db = self.db();
let ast::Arguments {
@@ -6211,101 +6211,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
node_index: _,
} = &call_expr.arguments;
for keyword in keywords {
self.infer_expression(&keyword.value, TypeContext::default());
if !keywords.is_empty() {
return None;
}
let [name_arg, bases_arg, namespace_arg] = match &**args {
[single] => {
let arg_type = self.infer_expression(single, TypeContext::default());
return if keywords.is_empty() {
arg_type.dunder_class(db)
} else {
if keywords.iter().any(|keyword| keyword.arg.is_some())
&& let Some(builder) =
self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr)
{
let mut diagnostic = builder
.into_diagnostic("No overload of class `type` matches arguments");
diagnostic.help(format_args!(
"`builtins.type()` expects no keyword arguments",
));
}
SubclassOfType::subclass_of_unknown()
};
}
[first, second] if second.is_starred_expr() => {
self.infer_expression(first, TypeContext::default());
self.infer_expression(second, TypeContext::default());
match &**keywords {
[single] if single.arg.is_none() => {
return SubclassOfType::subclass_of_unknown();
}
_ => {
if let Some(builder) =
self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr)
{
let mut diagnostic = builder
.into_diagnostic("No overload of class `type` matches arguments");
diagnostic.help(format_args!(
"`builtins.type()` expects no keyword arguments",
));
}
return SubclassOfType::subclass_of_unknown();
}
}
}
[name, bases, namespace] => [name, bases, namespace],
_ => {
for arg in args {
self.infer_expression(arg, TypeContext::default());
}
if let Some(builder) = self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr) {
let mut diagnostic =
builder.into_diagnostic("No overload of class `type` matches arguments");
diagnostic.help(format_args!(
"`builtins.type()` can either be called with one or three \
positional arguments (got {})",
args.len()
));
}
return SubclassOfType::subclass_of_unknown();
}
let [name_arg, bases_arg, namespace_arg] = &**args else {
return None;
};
// If any argument is a starred expression, we can't know how many positional arguments
// we're receiving, so fall back to normal call binding.
if args.iter().any(ast::Expr::is_starred_expr) {
return None;
}
// Infer the argument types.
let name_type = self.infer_expression(name_arg, TypeContext::default());
let bases_type = self.infer_expression(bases_arg, TypeContext::default());
let namespace_type = self.infer_expression(namespace_arg, TypeContext::default());
// TODO: validate other keywords against `__init_subclass__` methods of superclasses
if keywords
.iter()
.filter_map(|keyword| keyword.arg.as_deref())
.contains("metaclass")
{
if let Some(builder) = self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr) {
let mut diagnostic =
builder.into_diagnostic("No overload of class `type` matches arguments");
diagnostic
.help("The `metaclass` keyword argument is not supported in `type()` calls");
}
}
// If any argument is a starred expression, we can't know how many positional arguments
// we're receiving, so fall back to `type[Unknown]` to avoid false-positive errors.
if args.iter().any(ast::Expr::is_starred_expr) {
return SubclassOfType::subclass_of_unknown();
}
// Extract members from the namespace dict (third argument).
// Infer the whole dict first to avoid double-inferring individual values.
let namespace_type = self.infer_expression(namespace_arg, TypeContext::default());
let (members, has_dynamic_namespace): (Box<[(ast::name::Name, Type<'db>)]>, bool) =
if let ast::Expr::Dict(dict) = namespace_arg {
// Check if all keys are string literal types. If any key is not a string literal
@@ -6332,7 +6258,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.collect();
(members, !all_keys_are_string_literals)
} else if let Type::TypedDict(typed_dict) = namespace_type {
// `namespace` is a TypedDict instance. Extract known keys as members.
// Namespace is a TypedDict instance. Extract known keys as members.
// TypedDicts are "open" (can have additional string keys), so this
// is still a dynamic namespace for unknown attributes.
let members: Box<[(ast::name::Name, Type<'db>)]> = typed_dict
@@ -6342,7 +6268,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.collect();
(members, true)
} else {
// `namespace` is not a dict literal, so it's dynamic.
// Namespace is not a dict literal, so it's dynamic.
(Box::new([]), true)
};
@@ -6384,33 +6310,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let (bases, mut disjoint_bases) =
self.extract_dynamic_type_bases(bases_arg, bases_type, &name);
let scope = self.scope();
// Create the anchor for identifying this dynamic class.
// - For assigned `type()` calls, the Definition uniquely identifies the class.
// - For dangling calls, compute a relative offset from the scope's node index.
let anchor = if let Some(def) = definition {
DynamicClassAnchor::Definition(def)
} else {
let call_node_index = call_expr.node_index().load();
let scope_anchor = scope.node(db).node_index().unwrap_or(NodeIndex::from(0));
let anchor_u32 = scope_anchor
.as_u32()
.expect("scope anchor should not be NodeIndex::NONE");
let call_u32 = call_node_index
.as_u32()
.expect("call node should not be NodeIndex::NONE");
DynamicClassAnchor::ScopeOffset {
scope,
offset: call_u32 - anchor_u32,
}
};
let dynamic_class = DynamicClassLiteral::new(
db,
name,
bases,
anchor,
definition,
members,
has_dynamic_namespace,
None,
@@ -6480,7 +6384,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
}
Type::ClassLiteral(ClassLiteral::Dynamic(dynamic_class))
Some(Type::ClassLiteral(ClassLiteral::Dynamic(dynamic_class)))
}
/// Extract base classes from the second argument of a `type()` call.
@@ -6517,130 +6421,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.iter()
.enumerate()
.map(|(idx, base)| {
let diagnostic_node = bases_tuple_elts
.and_then(|elts| elts.get(idx))
.unwrap_or(bases_node);
// First try the standard conversion.
if let Some(class_base) =
ClassBase::try_from_type(db, *base, placeholder_class)
{
// Check for special bases that are not allowed for dynamic classes.
// Dynamic classes can't be generic, protocols, TypedDicts, or enums.
match class_base {
ClassBase::Generic | ClassBase::TypedDict => {
if let Some(builder) =
self.context.report_lint(&INVALID_BASE, diagnostic_node)
{
let mut diagnostic = builder.into_diagnostic(
"Invalid base for class created via `type()`",
);
diagnostic.set_primary_message(format_args!(
"Has type `{}`",
base.display(db)
));
match class_base {
ClassBase::Generic => {
diagnostic.info(
"Classes created via `type()` cannot be generic",
);
diagnostic.info(format_args!(
"Consider using `class {name}(Generic[...]): ...` instead"
));
}
ClassBase::TypedDict => {
diagnostic.info(
"Classes created via `type()` cannot be TypedDicts",
);
diagnostic.info(format_args!(
"Consider using `TypedDict(\"{name}\", {{}})` instead"
));
}
_ => unreachable!(),
}
}
return ClassBase::unknown();
// Collect disjoint bases for instance-layout-conflict checking.
if let ClassBase::Class(base_class) = class_base {
if let Some(disjoint_base) = base_class.nearest_disjoint_base(db) {
disjoint_bases.insert(
disjoint_base,
idx,
base_class.class_literal(db),
);
}
ClassBase::Protocol => {
if let Some(builder) = self
.context
.report_lint(&UNSUPPORTED_DYNAMIC_BASE, diagnostic_node)
{
let mut diagnostic = builder.into_diagnostic(
"Unsupported base for class created via `type()`",
);
diagnostic.set_primary_message(format_args!(
"Has type `{}`",
base.display(db)
));
diagnostic.info(
"Classes created via `type()` cannot be protocols",
);
diagnostic.info(format_args!(
"Consider using `class {name}(Protocol): ...` instead"
));
}
return ClassBase::unknown();
}
ClassBase::Class(class_type) => {
// Check if base is @final (includes enums with members).
if class_type.is_final(db) {
if let Some(builder) = self
.context
.report_lint(&SUBCLASS_OF_FINAL_CLASS, diagnostic_node)
{
builder.into_diagnostic(format_args!(
"Class `{name}` cannot inherit from final class `{}`",
class_type.name(db)
));
}
return ClassBase::unknown();
}
// Enum subclasses require the EnumMeta metaclass, which
// expects special dict attributes that `type()` doesn't provide.
if let Some((static_class, _)) =
class_type.static_class_literal(db)
{
if is_enum_class_by_inheritance(db, static_class) {
if let Some(builder) = self
.context
.report_lint(&INVALID_BASE, diagnostic_node)
{
let mut diagnostic = builder.into_diagnostic(
"Invalid base for class created via `type()`",
);
diagnostic.set_primary_message(format_args!(
"Has type `{}`",
base.display(db)
));
diagnostic.info(
"Creating an enum class via `type()` is not supported",
);
diagnostic.info(format_args!(
"Consider using `Enum(\"{name}\", [])` instead"
));
}
return ClassBase::unknown();
}
}
// Collect disjoint bases for instance-layout-conflict checking.
if let Some(disjoint_base) = class_type.nearest_disjoint_base(db)
{
disjoint_bases.insert(
disjoint_base,
idx,
class_type.class_literal(db),
);
}
return class_base;
}
ClassBase::Dynamic(_) => return class_base,
}
return class_base;
}
let diagnostic_node = bases_tuple_elts
.and_then(|elts| elts.get(idx))
.unwrap_or(bases_node);
// If that fails, check if the type is "type-like" (e.g., `type[Base]`).
// For type-like bases we emit `unsupported-dynamic-base` and use
// `Unknown` to avoid cascading errors. For non-type-like bases (like
@@ -9713,13 +9514,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
return Type::TypedDict(typed_dict);
}
// Handle 3-argument `type(name, bases, dict)`.
if let Type::ClassLiteral(class) = callable_type
&& class.is_known(self.db(), KnownClass::Type)
{
return self.infer_builtins_type_call(call_expression, None);
}
// We don't call `Type::try_call`, because we want to perform type inference on the
// arguments after matching them to parameters, but before checking that the argument types
// are assignable to any parameter annotations.
@@ -9811,20 +9605,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
{
self.called_functions.insert(function);
}
// Warn when `final()` is called as a function (not a decorator).
// Type checkers cannot interpret this usage and will not prevent subclassing.
if function.is_known(self.db(), KnownFunction::Final) {
if let Some(builder) = self
.context
.report_lint(&INEFFECTIVE_FINAL, call_expression)
{
let mut diagnostic = builder.into_diagnostic(
"Type checkers will not prevent subclassing when `final()` is called as a function",
);
diagnostic.info("Use `@final` as a decorator on a class or method instead");
}
}
}
let class = match callable_type {
@@ -13541,16 +13321,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let value_node = subscript.value.as_ref();
let inferred = match (value_ty, slice_ty) {
(Type::Dynamic(_) | Type::Never, _) => Some(value_ty),
(Type::TypeAlias(alias), _) => Some(self.infer_subscript_expression_types(
subscript,
alias.value_type(self.db()),
slice_ty,
expr_context,
)),
(_, Type::TypeAlias(alias)) => Some(self.infer_subscript_expression_types(
subscript,
value_ty,
alias.value_type(self.db()),
expr_context,
)),
(Type::Union(union), _) => Some(union.map(db, |element| {
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
})),
(_, Type::Union(union)) => Some(union.map(db, |element| {
self.infer_subscript_expression_types(subscript, value_ty, *element, expr_context)
})),
// TODO: we can map over the intersection and fold the results back into an intersection,
// but we need to make sure we avoid emitting a diagnostic if one positive element has a `__getitem__`
// method but another does not. This means `infer_subscript_expression_types`
// needs to return a `Result` rather than eagerly emitting diagnostics.
(Type::Intersection(_), _) => {
Some(todo_type!("Subscript expressions on intersections"))
(Type::Intersection(_), _) | (_, Type::Intersection(_)) => {
Some(todo_type!("Subscript expressions with intersections"))
}
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
@@ -13630,6 +13430,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}),
(Type::LiteralString, Type::IntLiteral(_) | Type::BooleanLiteral(_)) => {
Some(Type::LiteralString)
}
(Type::LiteralString, Type::NominalInstance(nominal))
if nominal.slice_literal(db).is_some() =>
{
Some(Type::LiteralString)
}
// Ex) Given `b"value"[1]`, return `97` (i.e., `ord(b"a")`)
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
i32::try_from(i64_int).ok().map(|i32_int| {
@@ -13761,7 +13571,39 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(todo_type!("Inference of subscript on special form"))
}
_ => None,
(
Type::FunctionLiteral(_)
| Type::WrapperDescriptor(_)
| Type::BoundMethod(_)
| Type::DataclassDecorator(_)
| Type::DataclassTransformer(_)
| Type::Callable(_)
| Type::ModuleLiteral(_)
| Type::ClassLiteral(_)
| Type::GenericAlias(_)
| Type::SubclassOf(_)
| Type::AlwaysFalsy
| Type::AlwaysTruthy
| Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::ProtocolInstance(_)
| Type::PropertyInstance(_)
| Type::EnumLiteral(_)
| Type::BoundSuper(_)
| Type::TypeIs(_)
| Type::TypeGuard(_)
| Type::TypedDict(_)
| Type::NewTypeInstance(_)
| Type::NominalInstance(_)
| Type::SpecialForm(_)
| Type::KnownInstance(_)
| Type::StringLiteral(_)
| Type::BytesLiteral(_)
| Type::LiteralString
| Type::TypeVar(_) // TODO: more complex logic required here!
| Type::KnownBoundMethod(_),
_,
) => None,
};
if let Some(inferred) = inferred {

View File

@@ -303,14 +303,14 @@ impl<'db> TypedDictType<'db> {
pub fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
match self {
TypedDictType::Class(defining_class) => defining_class.definition(db),
TypedDictType::Class(defining_class) => Some(defining_class.definition(db)),
TypedDictType::Synthesized(_) => None,
}
}
pub fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
match self {
TypedDictType::Class(defining_class) => defining_class.type_definition(db),
TypedDictType::Class(defining_class) => Some(defining_class.type_definition(db)),
TypedDictType::Synthesized(_) => None,
}
}

10
ty.schema.json generated
View File

@@ -520,16 +520,6 @@
}
]
},
"ineffective-final": {
"title": "detects calls to `final()` that type checkers cannot interpret",
"description": "## What it does\nChecks for calls to `final()` that type checkers cannot interpret.\n\n## Why is this bad?\nThe `final()` function is designed to be used as a decorator. When called directly\nas a function (e.g., `final(type(...))`), type checkers will not understand the\napplication of `final` and will not prevent subclassing.\n\n## Example\n\n```python\nfrom typing import final\n\n# Incorrect: type checkers will not prevent subclassing\nMyClass = final(type(\"MyClass\", (), {}))\n\n# Correct: use `final` as a decorator\n@final\nclass MyClass: ...\n```",
"default": "warn",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"instance-layout-conflict": {
"title": "detects class definitions that raise `TypeError` due to instance layout conflict",
"description": "## What it does\nChecks for classes definitions which will fail at runtime due to\n\"instance memory layout conflicts\".\n\nThis error is usually caused by attempting to combine multiple classes\nthat define non-empty `__slots__` in a class's [Method Resolution Order]\n(MRO), or by attempting to combine multiple builtin classes in a class's\nMRO.\n\n## Why is this bad?\nInheriting from bases with conflicting instance memory layouts\nwill lead to a `TypeError` at runtime.\n\nAn instance memory layout conflict occurs when CPython cannot determine\nthe memory layout instances of a class should have, because the instance\nmemory layout of one of its bases conflicts with the instance memory layout\nof one or more of its other bases.\n\nFor example, if a Python class defines non-empty `__slots__`, this will\nimpact the memory layout of instances of that class. Multiple inheritance\nfrom more than one different class defining non-empty `__slots__` is not\nallowed:\n\n```python\nclass A:\n __slots__ = (\"a\", \"b\")\n\nclass B:\n __slots__ = (\"a\", \"b\") # Even if the values are the same\n\n# TypeError: multiple bases have instance lay-out conflict\nclass C(A, B): ...\n```\n\nAn instance layout conflict can also be caused by attempting to use\nmultiple inheritance with two builtin classes, due to the way that these\nclasses are implemented in a CPython C extension:\n\n```python\nclass A(int, float): ... # TypeError: multiple bases have instance lay-out conflict\n```\n\nNote that pure-Python classes with no `__slots__`, or pure-Python classes\nwith empty `__slots__`, are always compatible:\n\n```python\nclass A: ...\nclass B:\n __slots__ = ()\nclass C:\n __slots__ = (\"a\", \"b\")\n\n# fine\nclass D(A, B, C): ...\n```\n\n## Known problems\nClasses that have \"dynamic\" definitions of `__slots__` (definitions do not consist\nof string literals, or tuples of string literals) are not currently considered disjoint\nbases by ty.\n\nAdditionally, this check is not exhaustive: many C extensions (including several in\nthe standard library) define classes that use extended memory layouts and thus cannot\ncoexist in a single MRO. Since it is currently not possible to represent this fact in\nstub files, having a full knowledge of these classes is also impossible. When it comes\nto classes that do not define `__slots__` at the Python level, therefore, ty, currently\nonly hard-codes a number of cases where it knows that a class will produce instances with\nan atypical memory layout.\n\n## Further reading\n- [CPython documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)\n- [CPython documentation: Method Resolution Order](https://docs.python.org/3/glossary.html#term-method-resolution-order)\n\n[Method Resolution Order]: https://docs.python.org/3/glossary.html#term-method-resolution-order",