mirror of https://github.com/astral-sh/ruff
[ty] Check method definitions on subclasses for Liskov violations (#21436)
This commit is contained in:
parent
aec225d825
commit
e642874cf1
|
|
@ -39,7 +39,7 @@ def test(): -> "int":
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L121" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L126" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L165" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L170" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ f(int) # error
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L191" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L196" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ a = 1
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L216" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ class C(A, B): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L242" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L247" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ class B(A): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L307" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ class B(A, A): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L328" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L333" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -329,7 +329,7 @@ def test(): -> "Literal[5]":
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L532" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L537" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -359,7 +359,7 @@ class C(A, B): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L556" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L561" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L360" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -474,7 +474,7 @@ an atypical memory layout.
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L610" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L615" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L650" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -529,7 +529,7 @@ a: int = ''
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1809" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1814" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L672" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -599,7 +599,7 @@ asyncio.run(main())
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L702" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base]
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L753" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L758" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -650,7 +650,7 @@ with 1:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L774" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -679,7 +679,7 @@ a: str
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L797" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L802" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -723,7 +723,7 @@ except ZeroDivisionError:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L833" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L838" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -756,7 +756,7 @@ class C[U](Generic[T]): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L577" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L582" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -795,7 +795,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L859" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L864" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -830,7 +830,7 @@ def f(t: TypeVar("U")): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L956" target="_blank">View source</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>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -858,13 +858,99 @@ class B(metaclass=f): ...
|
||||||
|
|
||||||
- [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
|
- [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
|
||||||
|
|
||||||
|
## `invalid-method-override`
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Default level: <a href="../rules.md#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#L1942" target="_blank">View source</a>
|
||||||
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
**What it does**
|
||||||
|
|
||||||
|
Detects method overrides that violate the [Liskov Substitution Principle] ("LSP").
|
||||||
|
|
||||||
|
The LSP states that an instance of a subtype should be substitutable for an instance of its supertype.
|
||||||
|
Applied to Python, this means:
|
||||||
|
1. All argument combinations a superclass method accepts
|
||||||
|
must also be accepted by an overriding subclass method.
|
||||||
|
2. The return type of an overriding subclass method must be a subtype
|
||||||
|
of the return type of the superclass method.
|
||||||
|
|
||||||
|
**Why is this bad?**
|
||||||
|
|
||||||
|
Violating the Liskov Substitution Principle will lead to many of ty's assumptions and
|
||||||
|
inferences being incorrect, which will mean that it will fail to catch many possible
|
||||||
|
type errors in your code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Super:
|
||||||
|
def method(self, x) -> int:
|
||||||
|
return 42
|
||||||
|
|
||||||
|
class Sub(Super):
|
||||||
|
# Liskov violation: `str` is not a subtype of `int`,
|
||||||
|
# but the supertype method promises to return an `int`.
|
||||||
|
def method(self, x) -> str: # error: [invalid-override]
|
||||||
|
return "foo"
|
||||||
|
|
||||||
|
def accepts_super(s: Super) -> int:
|
||||||
|
return s.method(x=42)
|
||||||
|
|
||||||
|
accepts_super(Sub()) # The result of this call is a string, but ty will infer
|
||||||
|
# it to be an `int` due to the violation of the Liskov Substitution Principle.
|
||||||
|
|
||||||
|
class Sub2(Super):
|
||||||
|
# Liskov violation: the superclass method can be called with a `x=`
|
||||||
|
# keyword argument, but the subclass method does not accept it.
|
||||||
|
def method(self, y) -> int: # error: [invalid-override]
|
||||||
|
return 42
|
||||||
|
|
||||||
|
accepts_super(Sub2()) # TypeError at runtime: method() got an unexpected keyword argument 'x'
|
||||||
|
# ty cannot catch this error due to the violation of the Liskov Substitution Principle.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common issues**
|
||||||
|
|
||||||
|
|
||||||
|
**Why does ty complain about my `__eq__` method?**
|
||||||
|
|
||||||
|
|
||||||
|
`__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary
|
||||||
|
objects as their second argument, for example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class A:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
# gracefully handle an object of an unexpected type
|
||||||
|
# without raising an exception
|
||||||
|
if not isinstance(other, A):
|
||||||
|
return False
|
||||||
|
return self.x == other.x
|
||||||
|
```
|
||||||
|
|
||||||
|
If `A.__eq__` here were annotated as only accepting `A` instances for its second argument,
|
||||||
|
it would imply that you wouldn't be able to use `==` between instances of `A` and
|
||||||
|
instances of unrelated classes without an exception possibly being raised. While some
|
||||||
|
classes in Python do indeed behave this way, the strongly held convention is that it should
|
||||||
|
be avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__`
|
||||||
|
and `__ne__` methods accept `object` as their second argument.
|
||||||
|
|
||||||
|
[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
|
||||||
|
|
||||||
## `invalid-named-tuple`
|
## `invalid-named-tuple`
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L506" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -896,7 +982,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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>) ·
|
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/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#L932" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L937" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -926,7 +1012,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L983" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -976,7 +1062,7 @@ def foo(x: int) -> int: ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1082" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1002,7 +1088,7 @@ def f(a: int = ''): ...
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L887" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L892" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1033,7 +1119,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L442" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1067,7 +1153,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1102" target="_blank">View source</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>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1116,7 +1202,7 @@ def g():
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L631" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L636" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1141,7 +1227,7 @@ def func() -> int:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1145" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1199,7 +1285,7 @@ TODO #14889
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L911" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L916" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1226,7 +1312,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1184" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1189" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1256,7 +1342,7 @@ TYPE_CHECKING = ''
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1208" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1286,7 +1372,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1260" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1265" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1320,7 +1406,7 @@ f(10) # Error
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1232" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1237" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1354,7 +1440,7 @@ class C:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1288" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1389,7 +1475,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1317" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1322" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1414,7 +1500,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1910" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1915" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1447,7 +1533,7 @@ alice["age"] # KeyError
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1336" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1341" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1476,7 +1562,7 @@ func("string") # error: [no-matching-overload]
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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%20non-subscriptable" target="_blank">Related issues</a> ·
|
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" 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#L1364" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1500,7 +1586,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1377" 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>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1526,7 +1612,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1428" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1553,7 +1639,7 @@ f(1, x=2) # Error raised here
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1663" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1668" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1611,7 +1697,7 @@ def test(): -> "int":
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1785" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1790" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1641,7 +1727,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1519" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1524" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1670,7 +1756,7 @@ class B(A): ... # Error raised here
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1564" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1697,7 +1783,7 @@ f("foo") # Error raised here
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1542" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1547" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1725,7 +1811,7 @@ def _(x: int):
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1585" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1590" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1771,7 +1857,7 @@ class A:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1642" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1647" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1798,7 +1884,7 @@ f(x=1, y=2) # Error raised here
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1684" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1689" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1826,7 +1912,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1706" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1711" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1851,7 +1937,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1725" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1730" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1876,7 +1962,7 @@ print(x) # NameError: name 'x' is not defined
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1397" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1402" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1913,7 +1999,7 @@ b1 < b2 < b1 # exception raised here
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1744" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1941,7 +2027,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1766" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1771" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1966,7 +2052,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L471" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L476" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2007,7 +2093,7 @@ class SubProto(BaseProto, Protocol):
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L286" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L291" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2095,7 +2181,7 @@ a = 20 / 0 # type: ignore
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1449" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2123,7 +2209,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L139" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2155,7 +2241,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1471" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1476" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2187,7 +2273,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1837" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1842" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2214,7 +2300,7 @@ cast(int, f()) # Redundant
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1624" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1629" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2238,7 +2324,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1858" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1863" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2296,7 +2382,7 @@ def g():
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L720" target="_blank">View source</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>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2335,7 +2421,7 @@ class D(C): ... # error: [unsupported-base]
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1026" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1031" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2398,7 +2484,7 @@ def foo(x: int | str) -> int | str:
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
Default level: <a href="../rules.md#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>) ·
|
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/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#L268" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L273" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2422,7 +2508,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
Default level: <a href="../rules.md#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> ·
|
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/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#L1497" target="_blank">View source</a>
|
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1502" target="_blank">View source</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1904,6 +1904,7 @@ we only consider the attribute assignment to be valid if the assigned attribute
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
class Date:
|
class Date:
|
||||||
|
# error: [invalid-method-override]
|
||||||
def __setattr__(self, name: Literal["day", "month", "year"], value: int) -> None:
|
def __setattr__(self, name: Literal["day", "month", "year"], value: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -501,8 +501,8 @@ class A[T]:
|
||||||
return a
|
return a
|
||||||
|
|
||||||
class B[T](A[T]):
|
class B[T](A[T]):
|
||||||
def f(self, b: T) -> T:
|
def f(self, a: T) -> T:
|
||||||
return super().f(b)
|
return super().f(a)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Invalid Usages
|
## Invalid Usages
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ class GtReturnType: ...
|
||||||
class GeReturnType: ...
|
class GeReturnType: ...
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def __eq__(self, other: A) -> EqReturnType:
|
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||||
return EqReturnType()
|
return EqReturnType()
|
||||||
|
|
||||||
def __ne__(self, other: A) -> NeReturnType:
|
def __ne__(self, other: A) -> NeReturnType: # error: [invalid-method-override]
|
||||||
return NeReturnType()
|
return NeReturnType()
|
||||||
|
|
||||||
def __lt__(self, other: A) -> LtReturnType:
|
def __lt__(self, other: A) -> LtReturnType:
|
||||||
|
|
@ -66,10 +66,10 @@ class GtReturnType: ...
|
||||||
class GeReturnType: ...
|
class GeReturnType: ...
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def __eq__(self, other: B) -> EqReturnType:
|
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||||
return EqReturnType()
|
return EqReturnType()
|
||||||
|
|
||||||
def __ne__(self, other: B) -> NeReturnType:
|
def __ne__(self, other: B) -> NeReturnType: # error: [invalid-method-override]
|
||||||
return NeReturnType()
|
return NeReturnType()
|
||||||
|
|
||||||
def __lt__(self, other: B) -> LtReturnType:
|
def __lt__(self, other: B) -> LtReturnType:
|
||||||
|
|
@ -111,10 +111,10 @@ class GtReturnType: ...
|
||||||
class GeReturnType: ...
|
class GeReturnType: ...
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def __eq__(self, other: B) -> EqReturnType:
|
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||||
return EqReturnType()
|
return EqReturnType()
|
||||||
|
|
||||||
def __ne__(self, other: B) -> NeReturnType:
|
def __ne__(self, other: B) -> NeReturnType: # error: [invalid-method-override]
|
||||||
return NeReturnType()
|
return NeReturnType()
|
||||||
|
|
||||||
def __lt__(self, other: B) -> LtReturnType:
|
def __lt__(self, other: B) -> LtReturnType:
|
||||||
|
|
@ -132,12 +132,10 @@ class A:
|
||||||
class Unrelated: ...
|
class Unrelated: ...
|
||||||
|
|
||||||
class B:
|
class B:
|
||||||
# To override builtins.object.__eq__ and builtins.object.__ne__
|
def __eq__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||||
# TODO these should emit an invalid override diagnostic
|
|
||||||
def __eq__(self, other: Unrelated) -> B:
|
|
||||||
return B()
|
return B()
|
||||||
|
|
||||||
def __ne__(self, other: Unrelated) -> B:
|
def __ne__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||||
return B()
|
return B()
|
||||||
|
|
||||||
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
|
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
|
||||||
|
|
@ -180,10 +178,10 @@ class GtReturnType: ...
|
||||||
class GeReturnType: ...
|
class GeReturnType: ...
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def __eq__(self, other: A) -> A:
|
def __eq__(self, other: A) -> A: # error: [invalid-method-override]
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
def __ne__(self, other: A) -> A:
|
def __ne__(self, other: A) -> A: # error: [invalid-method-override]
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
def __lt__(self, other: A) -> A:
|
def __lt__(self, other: A) -> A:
|
||||||
|
|
@ -199,22 +197,22 @@ class A:
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
class B(A):
|
class B(A):
|
||||||
def __eq__(self, other: A) -> EqReturnType:
|
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||||
return EqReturnType()
|
return EqReturnType()
|
||||||
|
|
||||||
def __ne__(self, other: A) -> NeReturnType:
|
def __ne__(self, other: A) -> NeReturnType: # error: [invalid-method-override]
|
||||||
return NeReturnType()
|
return NeReturnType()
|
||||||
|
|
||||||
def __lt__(self, other: A) -> LtReturnType:
|
def __lt__(self, other: A) -> LtReturnType: # error: [invalid-method-override]
|
||||||
return LtReturnType()
|
return LtReturnType()
|
||||||
|
|
||||||
def __le__(self, other: A) -> LeReturnType:
|
def __le__(self, other: A) -> LeReturnType: # error: [invalid-method-override]
|
||||||
return LeReturnType()
|
return LeReturnType()
|
||||||
|
|
||||||
def __gt__(self, other: A) -> GtReturnType:
|
def __gt__(self, other: A) -> GtReturnType: # error: [invalid-method-override]
|
||||||
return GtReturnType()
|
return GtReturnType()
|
||||||
|
|
||||||
def __ge__(self, other: A) -> GeReturnType:
|
def __ge__(self, other: A) -> GeReturnType: # error: [invalid-method-override]
|
||||||
return GeReturnType()
|
return GeReturnType()
|
||||||
|
|
||||||
reveal_type(A() == B()) # revealed: EqReturnType
|
reveal_type(A() == B()) # revealed: EqReturnType
|
||||||
|
|
@ -243,10 +241,10 @@ class A:
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
class B(A):
|
class B(A):
|
||||||
def __lt__(self, other: int) -> B:
|
def __lt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||||
return B()
|
return B()
|
||||||
|
|
||||||
def __gt__(self, other: int) -> B:
|
def __gt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||||
return B()
|
return B()
|
||||||
|
|
||||||
reveal_type(A() < B()) # revealed: A
|
reveal_type(A() < B()) # revealed: A
|
||||||
|
|
@ -291,11 +289,10 @@ Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#ob
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
# TODO both these overrides should emit invalid-override diagnostic
|
def __eq__(self, other: int) -> A: # error: [invalid-method-override]
|
||||||
def __eq__(self, other: int) -> A:
|
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
def __ne__(self, other: int) -> A:
|
def __ne__(self, other: int) -> A: # error: [invalid-method-override]
|
||||||
return A()
|
return A()
|
||||||
|
|
||||||
reveal_type(A() == A()) # revealed: bool
|
reveal_type(A() == A()) # revealed: bool
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,10 @@ class GtReturnType: ...
|
||||||
class GeReturnType: ...
|
class GeReturnType: ...
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def __eq__(self, o: object) -> EqReturnType:
|
def __eq__(self, o: object) -> EqReturnType: # error: [invalid-method-override]
|
||||||
return EqReturnType()
|
return EqReturnType()
|
||||||
|
|
||||||
def __ne__(self, o: object) -> NeReturnType:
|
def __ne__(self, o: object) -> NeReturnType: # error: [invalid-method-override]
|
||||||
return NeReturnType()
|
return NeReturnType()
|
||||||
|
|
||||||
def __lt__(self, o: A) -> LtReturnType:
|
def __lt__(self, o: A) -> LtReturnType:
|
||||||
|
|
@ -386,6 +386,7 @@ class NotBoolable:
|
||||||
__bool__: None = None
|
__bool__: None = None
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
|
# error: [invalid-method-override]
|
||||||
def __eq__(self, other) -> NotBoolable:
|
def __eq__(self, other) -> NotBoolable:
|
||||||
return NotBoolable()
|
return NotBoolable()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,7 @@ class UnknownLengthSubclassWithDunderLenOverridden(tuple[int, ...]):
|
||||||
reveal_type(len(UnknownLengthSubclassWithDunderLenOverridden())) # revealed: Literal[42]
|
reveal_type(len(UnknownLengthSubclassWithDunderLenOverridden())) # revealed: Literal[42]
|
||||||
|
|
||||||
class FixedLengthSubclassWithDunderLenOverridden(tuple[int]):
|
class FixedLengthSubclassWithDunderLenOverridden(tuple[int]):
|
||||||
# TODO: we should complain about this as a Liskov violation (incompatible override)
|
def __len__(self) -> Literal[42]: # error: [invalid-method-override]
|
||||||
def __len__(self) -> Literal[42]:
|
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
reveal_type(len(FixedLengthSubclassWithDunderLenOverridden((1,)))) # revealed: Literal[42]
|
reveal_type(len(FixedLengthSubclassWithDunderLenOverridden((1,)))) # revealed: Literal[42]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,525 @@
|
||||||
|
# The Liskov Substitution Principle
|
||||||
|
|
||||||
|
The Liskov Substitution Principle provides the basis for many of the assumptions a type checker
|
||||||
|
generally makes about types in Python:
|
||||||
|
|
||||||
|
> Subtype Requirement: Let `ϕ(x)` be a property provable about objects `x` of type `T`. Then
|
||||||
|
> `ϕ(y)` should be true for objects `y` of type `S` where `S` is a subtype of `T`.
|
||||||
|
|
||||||
|
In order for a type checker's assumptions to be sound, it is crucial for the type checker to enforce
|
||||||
|
the Liskov Substitution Principle on code that it checks. In practice, this usually manifests as
|
||||||
|
several checks for a type checker to perform when it checks a subclass `B` of a class `A`:
|
||||||
|
|
||||||
|
1. Read-only attributes should only ever be overridden covariantly: if a property `A.p` resolves to
|
||||||
|
`int` when accessed, accessing `B.p` should either resolve to `int` or a subtype of `int`.
|
||||||
|
1. Method return types should only ever be overridden covariantly: if a method `A.f` returns `int`
|
||||||
|
when called, calling `B.f` should also resolve to `int or a subtype of`int\`.
|
||||||
|
1. Method parameters should only ever be overridden contravariantly: if a method `A.f` can be called
|
||||||
|
with an argument of type `bool`, then the method `B.f` must also be callable with type `bool`
|
||||||
|
(though it is permitted for the override to also accept other types)
|
||||||
|
1. Mutable attributes should only ever be overridden invariantly: if a mutable attribute `A.attr`
|
||||||
|
resolves to type `str`, it can only be overridden on a subclass with exactly the same type.
|
||||||
|
|
||||||
|
## Method return types
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class Super:
|
||||||
|
def method(self) -> int: ...
|
||||||
|
|
||||||
|
class Sub1(Super):
|
||||||
|
def method(self) -> int: ... # fine
|
||||||
|
|
||||||
|
class Sub2(Super):
|
||||||
|
def method(self) -> bool: ... # fine: `bool` is a subtype of `int`
|
||||||
|
|
||||||
|
class Sub3(Super):
|
||||||
|
def method(self) -> object: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub4(Super):
|
||||||
|
def method(self) -> str: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method parameters
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class Super:
|
||||||
|
def method(self, x: int, /): ...
|
||||||
|
|
||||||
|
class Sub1(Super):
|
||||||
|
def method(self, x: int, /): ... # fine
|
||||||
|
|
||||||
|
class Sub2(Super):
|
||||||
|
def method(self, x: object, /): ... # fine: `method` still accepts any argument of type `int`
|
||||||
|
|
||||||
|
class Sub4(Super):
|
||||||
|
def method(self, x: int | str, /): ... # fine
|
||||||
|
|
||||||
|
class Sub5(Super):
|
||||||
|
def method(self, x: int): ... # fine: `x` can still be passed positionally
|
||||||
|
|
||||||
|
class Sub6(Super):
|
||||||
|
# fine: `method()` can still be called with just a single argument
|
||||||
|
def method(self, x: int, *args): ...
|
||||||
|
|
||||||
|
class Sub7(Super):
|
||||||
|
def method(self, x: int, **kwargs): ... # fine
|
||||||
|
|
||||||
|
class Sub8(Super):
|
||||||
|
def method(self, x: int, *args, **kwargs): ... # fine
|
||||||
|
|
||||||
|
class Sub9(Super):
|
||||||
|
def method(self, x: int, extra_positional_arg=42, /): ... # fine
|
||||||
|
|
||||||
|
class Sub10(Super):
|
||||||
|
def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine
|
||||||
|
|
||||||
|
class Sub11(Super):
|
||||||
|
def method(self, x: int, *, extra_kw_only_arg=42): ... # fine
|
||||||
|
|
||||||
|
class Sub12(Super):
|
||||||
|
# Some calls permitted by the superclass are now no longer allowed
|
||||||
|
# (the method can no longer be passed any arguments!)
|
||||||
|
def method(self, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub13(Super):
|
||||||
|
# Some calls permitted by the superclass are now no longer allowed
|
||||||
|
# (the method can no longer be passed exactly one argument!)
|
||||||
|
def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub14(Super):
|
||||||
|
# Some calls permitted by the superclass are now no longer allowed
|
||||||
|
# (x can no longer be passed positionally!)
|
||||||
|
def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub15(Super):
|
||||||
|
# Some calls permitted by the superclass are now no longer allowed
|
||||||
|
# (x can no longer be passed any integer -- it now requires a bool!)
|
||||||
|
def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Super2:
|
||||||
|
def method2(self, x): ...
|
||||||
|
|
||||||
|
class Sub16(Super2):
|
||||||
|
def method2(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub17(Super2):
|
||||||
|
def method2(self, *, x): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Super3:
|
||||||
|
def method3(self, *, x): ...
|
||||||
|
|
||||||
|
class Sub18(Super3):
|
||||||
|
def method3(self, x): ... # fine: `x` can still be used as a keyword argument
|
||||||
|
|
||||||
|
class Sub19(Super3):
|
||||||
|
def method3(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Super4:
|
||||||
|
def method(self, *args: int, **kwargs: str): ...
|
||||||
|
|
||||||
|
class Sub20(Super4):
|
||||||
|
def method(self, *args: object, **kwargs: object): ... # fine
|
||||||
|
|
||||||
|
class Sub21(Super4):
|
||||||
|
def method(self, *args): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub22(Super4):
|
||||||
|
def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Sub23(Super4):
|
||||||
|
def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## The entire class hierarchy is checked
|
||||||
|
|
||||||
|
If a child class's method definition is Liskov-compatible with the method definition on its parent
|
||||||
|
class, Liskov compatibility must also nonetheless be checked with respect to the method definition
|
||||||
|
on its grandparent class. This is because type checkers will treat the child class as a subtype of
|
||||||
|
the grandparent class just as much as they treat it as a subtype of the parent class, so
|
||||||
|
substitutability with respect to the grandparent class is just as important:
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
`stub.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
class Grandparent:
|
||||||
|
def method(self, x: int) -> None: ...
|
||||||
|
|
||||||
|
class Parent(Grandparent):
|
||||||
|
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class Child(Parent):
|
||||||
|
# compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||||
|
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class OtherChild(Parent):
|
||||||
|
# compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||||
|
def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class GradualParent(Grandparent):
|
||||||
|
def method(self, x: Any) -> None: ...
|
||||||
|
|
||||||
|
class ThirdChild(GradualParent):
|
||||||
|
# `GradualParent.method` is compatible with the signature of `Grandparent.method`,
|
||||||
|
# and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||||
|
# but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||||
|
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
`other_stub.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class A:
|
||||||
|
def get(self, default): ...
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def get(self, default, /): ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
get = 56
|
||||||
|
|
||||||
|
class C(B):
|
||||||
|
# `get` appears in the symbol table of `C`,
|
||||||
|
# but that doesn't confuse our diagnostic...
|
||||||
|
foo = get
|
||||||
|
|
||||||
|
class D(C):
|
||||||
|
# compatible with `C.get` and `B.get`, but not with `A.get`
|
||||||
|
def get(self, my_default): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-generic methods on generic classes work as expected
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class A[T]:
|
||||||
|
def method(self, x: T) -> None: ...
|
||||||
|
|
||||||
|
class B[T](A[T]):
|
||||||
|
def method(self, x: T) -> None: ... # fine
|
||||||
|
|
||||||
|
class C(A[int]):
|
||||||
|
def method(self, x: int) -> None: ... # fine
|
||||||
|
|
||||||
|
class D[T](A[T]):
|
||||||
|
def method(self, x: object) -> None: ... # fine
|
||||||
|
|
||||||
|
class E(A[int]):
|
||||||
|
def method(self, x: object) -> None: ... # fine
|
||||||
|
|
||||||
|
class F[T](A[T]):
|
||||||
|
# TODO: we should emit `invalid-method-override` on this:
|
||||||
|
# `str` is not necessarily a supertype of `T`!
|
||||||
|
def method(self, x: str) -> None: ...
|
||||||
|
|
||||||
|
class G(A[int]):
|
||||||
|
def method(self, x: bool) -> None: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generic methods on non-generic classes work as expected
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
from typing import Never, Self
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def method[T](self, x: T) -> T: ...
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def method[T](self, x: T) -> T: ... # fine
|
||||||
|
|
||||||
|
class C(A):
|
||||||
|
def method(self, x: object) -> Never: ... # fine
|
||||||
|
|
||||||
|
class D(A):
|
||||||
|
# TODO: we should emit [invalid-method-override] here:
|
||||||
|
# `A.method` accepts an argument of any type,
|
||||||
|
# but `D.method` only accepts `int`s
|
||||||
|
def method(self, x: int) -> int: ...
|
||||||
|
|
||||||
|
class A2:
|
||||||
|
def method(self, x: int) -> int: ...
|
||||||
|
|
||||||
|
class B2(A2):
|
||||||
|
# fine: although `B2.method()` will not always return an `int`,
|
||||||
|
# an instance of `B2` can be substituted wherever an instance of `A2` is expected,
|
||||||
|
# and it *will* always return an `int` if it is passed an `int`
|
||||||
|
# (which is all that will be allowed if an instance of `A2` is expected)
|
||||||
|
def method[T](self, x: T) -> T: ...
|
||||||
|
|
||||||
|
class C2(A2):
|
||||||
|
def method[T: int](self, x: T) -> T: ...
|
||||||
|
|
||||||
|
class D2(A2):
|
||||||
|
# The type variable is bound to a type disjoint from `int`,
|
||||||
|
# so the method will not accept integers, and therefore this is an invalid override
|
||||||
|
def method[T: str](self, x: T) -> T: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class A3:
|
||||||
|
def method(self) -> Self: ...
|
||||||
|
|
||||||
|
class B3(A3):
|
||||||
|
def method(self) -> Self: ... # fine
|
||||||
|
|
||||||
|
class C3(A3):
|
||||||
|
# TODO: should this be allowed?
|
||||||
|
# Mypy/pyright/pyrefly all allow it,
|
||||||
|
# but conceptually it seems similar to `B4.method` below,
|
||||||
|
# which mypy/pyrefly agree is a Liskov violation
|
||||||
|
# (pyright disagrees as of 20/11/2025: https://github.com/microsoft/pyright/issues/11128)
|
||||||
|
# when called on a subclass, `C3.method()` will not return an
|
||||||
|
# instance of that subclass
|
||||||
|
def method(self) -> C3: ...
|
||||||
|
|
||||||
|
class D3(A3):
|
||||||
|
def method(self: Self) -> Self: ... # fine
|
||||||
|
|
||||||
|
class E3(A3):
|
||||||
|
def method(self: E3) -> Self: ... # fine
|
||||||
|
|
||||||
|
class F3(A3):
|
||||||
|
def method(self: A3) -> Self: ... # fine
|
||||||
|
|
||||||
|
class G3(A3):
|
||||||
|
def method(self: object) -> Self: ... # fine
|
||||||
|
|
||||||
|
class H3(A3):
|
||||||
|
# TODO: we should emit `invalid-method-override` here
|
||||||
|
# (`A3.method()` can be called on any instance of `A3`,
|
||||||
|
# but `H3.method()` can only be called on objects that are
|
||||||
|
# instances of `str`)
|
||||||
|
def method(self: str) -> Self: ...
|
||||||
|
|
||||||
|
class I3(A3):
|
||||||
|
# TODO: we should emit `invalid-method-override` here
|
||||||
|
# (`I3.method()` cannot be called with any inhabited type!)
|
||||||
|
def method(self: Never) -> Self: ...
|
||||||
|
|
||||||
|
class A4:
|
||||||
|
def method[T: int](self, x: T) -> T: ...
|
||||||
|
|
||||||
|
class B4(A4):
|
||||||
|
# TODO: we should emit `invalid-method-override` here.
|
||||||
|
# `A4.method` promises that if it is passed a `bool`, it will return a `bool`,
|
||||||
|
# but this is not necessarily true for `B4.method`: if passed a `bool`,
|
||||||
|
# it could return a non-`bool` `int`!
|
||||||
|
def method(self, x: int) -> int: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generic methods on generic classes work as expected
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
from typing import Never
|
||||||
|
|
||||||
|
class A[T]:
|
||||||
|
def method[S](self, x: T, y: S) -> S: ...
|
||||||
|
|
||||||
|
class B[T](A[T]):
|
||||||
|
def method[S](self, x: T, y: S) -> S: ... # fine
|
||||||
|
|
||||||
|
class C(A[int]):
|
||||||
|
def method[S](self, x: int, y: S) -> S: ... # fine
|
||||||
|
|
||||||
|
class D[T](A[T]):
|
||||||
|
def method[S](self, x: object, y: S) -> S: ... # fine
|
||||||
|
|
||||||
|
class E(A[int]):
|
||||||
|
def method[S](self, x: object, y: S) -> S: ... # fine
|
||||||
|
|
||||||
|
class F(A[int]):
|
||||||
|
def method(self, x: object, y: object) -> Never: ... # fine
|
||||||
|
|
||||||
|
class A2[T]:
|
||||||
|
def method(self, x: T, y: int) -> int: ...
|
||||||
|
|
||||||
|
class B2[T](A2[T]):
|
||||||
|
def method[S](self, x: T, y: S) -> S: ... # fine
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fully qualified names are used in diagnostics where appropriate
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
`a.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class A:
|
||||||
|
def foo(self, x): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`b.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
import a
|
||||||
|
|
||||||
|
class A(a.A):
|
||||||
|
def foo(self, y): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excluded methods
|
||||||
|
|
||||||
|
Certain special constructor methods are excluded from Liskov checks. None of the following classes
|
||||||
|
cause us to emit any errors, therefore:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# This is so that the dataclasses machinery will generate `__replace__` methods for us
|
||||||
|
# (the synthesized `__replace__` methods should not be reported as invalid overrides!)
|
||||||
|
[environment]
|
||||||
|
python-version = "3.13"
|
||||||
|
```
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
class Grandparent: ...
|
||||||
|
class Parent(Grandparent):
|
||||||
|
def __new__(cls, x: int) -> Self: ...
|
||||||
|
def __init__(self, x: int) -> None: ...
|
||||||
|
|
||||||
|
class Child(Parent):
|
||||||
|
def __new__(cls, x: str, y: str) -> Self: ...
|
||||||
|
def __init__(self, x: str, y: str) -> Self: ...
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class DataSuper:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
def __post_init__(self, x: int) -> None:
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class DataSub(DataSuper):
|
||||||
|
y: str
|
||||||
|
|
||||||
|
def __post_init__(self, x: int, y: str) -> None:
|
||||||
|
self.y = y
|
||||||
|
super().__post_init__(x)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Edge case: function defined in another module and then assigned in a class body
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
`foo.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
def x(self, y: str): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`bar.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
import foo
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def x(self, y: int): ...
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
x = foo.x # error: [invalid-method-override]
|
||||||
|
|
||||||
|
class C:
|
||||||
|
x = foo.x
|
||||||
|
|
||||||
|
class D(C):
|
||||||
|
def x(self, y: int): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bad override of `__eq__`
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Bad:
|
||||||
|
x: int
|
||||||
|
def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||||
|
return self.x == other.x
|
||||||
|
```
|
||||||
|
|
||||||
|
## Synthesized methods
|
||||||
|
|
||||||
|
`NamedTuple` classes and dataclasses both have methods generated at runtime that do not have
|
||||||
|
source-code definitions. There are several scenarios to consider here:
|
||||||
|
|
||||||
|
1. A synthesized method on a superclass is overridden by a "normal" (not synthesized) method on a
|
||||||
|
subclass
|
||||||
|
1. A "normal" method on a superclass is overridden by a synthesized method on a subclass
|
||||||
|
1. A synthesized method on a superclass is overridden by a synthesized method on a subclass
|
||||||
|
|
||||||
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Foo:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
class Bar(Foo):
|
||||||
|
def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||||
|
|
||||||
|
# TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||||
|
# generated that is incompatible with the generated `__lt__` method on the superclass.
|
||||||
|
# We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
|
||||||
|
# be `invalid-method-override` since we'd emit it on the class definition rather than
|
||||||
|
# on any method definition. Note also that no other type checker complains about this
|
||||||
|
# as of 2025-11-21.
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Bar2(Foo):
|
||||||
|
y: str
|
||||||
|
|
||||||
|
# TODO: Although this class does not override any methods of `Foo`, the design of the
|
||||||
|
# `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
|
||||||
|
# Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
|
||||||
|
# expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
|
||||||
|
# and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
|
||||||
|
# be compared with instances of subclasses of `Foo`).
|
||||||
|
#
|
||||||
|
# Many users would probably like their type checkers to alert them to cases where instances
|
||||||
|
# of subclasses cannot be substituted for instances of superclasses, as this violates many
|
||||||
|
# assumptions a type checker will make and makes it likely that a type checker will fail to
|
||||||
|
# catch type errors elsewhere in the user's code. We could therefore consider treating all
|
||||||
|
# `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
|
||||||
|
# this probably shouldn't be reported with the same error code as Liskov violations, since
|
||||||
|
# the error does not stem from any method signatures written by the user. The example is
|
||||||
|
# only included here for completeness.
|
||||||
|
#
|
||||||
|
# Note that no other type checker catches this error as of 2025-11-21.
|
||||||
|
class Bar3(Foo): ...
|
||||||
|
|
||||||
|
class Eggs:
|
||||||
|
def __lt__(self, other: Eggs) -> bool: ...
|
||||||
|
|
||||||
|
# TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
|
||||||
|
# We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
|
||||||
|
# diagnostic here but pyright and pyrefly do not.
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Ham(Eggs):
|
||||||
|
x: int
|
||||||
|
|
||||||
|
class Baz(NamedTuple):
|
||||||
|
x: int
|
||||||
|
|
||||||
|
class Spam(Baz):
|
||||||
|
def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
@ -3069,18 +3069,15 @@ from typing import Protocol
|
||||||
from ty_extensions import static_assert, is_subtype_of, is_equivalent_to, is_disjoint_from
|
from ty_extensions import static_assert, is_subtype_of, is_equivalent_to, is_disjoint_from
|
||||||
|
|
||||||
class HasRepr(Protocol):
|
class HasRepr(Protocol):
|
||||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
# error: [invalid-method-override]
|
||||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasRepr`)
|
|
||||||
def __repr__(self) -> object: ...
|
def __repr__(self) -> object: ...
|
||||||
|
|
||||||
class HasReprRecursive(Protocol):
|
class HasReprRecursive(Protocol):
|
||||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
# error: [invalid-method-override]
|
||||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasReprRecursive`)
|
|
||||||
def __repr__(self) -> "HasReprRecursive": ...
|
def __repr__(self) -> "HasReprRecursive": ...
|
||||||
|
|
||||||
class HasReprRecursiveAndFoo(Protocol):
|
class HasReprRecursiveAndFoo(Protocol):
|
||||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
# error: [invalid-method-override]
|
||||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasReprRecursiveAndFoo`)
|
|
||||||
def __repr__(self) -> "HasReprRecursiveAndFoo": ...
|
def __repr__(self) -> "HasReprRecursiveAndFoo": ...
|
||||||
foo: int
|
foo: int
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Bad override of `__eq__`
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Bad:
|
||||||
|
2 | x: int
|
||||||
|
3 | def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||||
|
4 | return self.x == other.x
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `__eq__`
|
||||||
|
--> src/mdtest_snippet.py:3:9
|
||||||
|
|
|
||||||
|
1 | class Bad:
|
||||||
|
2 | x: int
|
||||||
|
3 | def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `object.__eq__`
|
||||||
|
4 | return self.x == other.x
|
||||||
|
|
|
||||||
|
::: stdlib/builtins.pyi:142:9
|
||||||
|
|
|
||||||
|
140 | def __setattr__(self, name: str, value: Any, /) -> None: ...
|
||||||
|
141 | def __delattr__(self, name: str, /) -> None: ...
|
||||||
|
142 | def __eq__(self, value: object, /) -> bool: ...
|
||||||
|
| -------------------------------------- `object.__eq__` defined here
|
||||||
|
143 | def __ne__(self, value: object, /) -> bool: ...
|
||||||
|
144 | def __str__(self) -> str: ... # noqa: Y029
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
help: It is recommended for `__eq__` to work with arbitrary objects, for example:
|
||||||
|
help
|
||||||
|
help: def __eq__(self, other: object) -> bool:
|
||||||
|
help: if not isinstance(other, Bad):
|
||||||
|
help: return False
|
||||||
|
help: return <logic to compare two `Bad` instances>
|
||||||
|
help
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Edge case: function defined in another module and then assigned in a class body
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## foo.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | def x(self, y: str): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## bar.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | import foo
|
||||||
|
2 |
|
||||||
|
3 | class A:
|
||||||
|
4 | def x(self, y: int): ...
|
||||||
|
5 |
|
||||||
|
6 | class B(A):
|
||||||
|
7 | x = foo.x # error: [invalid-method-override]
|
||||||
|
8 |
|
||||||
|
9 | class C:
|
||||||
|
10 | x = foo.x
|
||||||
|
11 |
|
||||||
|
12 | class D(C):
|
||||||
|
13 | def x(self, y: int): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `x`
|
||||||
|
--> src/bar.pyi:4:9
|
||||||
|
|
|
||||||
|
3 | class A:
|
||||||
|
4 | def x(self, y: int): ...
|
||||||
|
| --------------- `A.x` defined here
|
||||||
|
5 |
|
||||||
|
6 | class B(A):
|
||||||
|
7 | x = foo.x # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^ Definition is incompatible with `A.x`
|
||||||
|
8 |
|
||||||
|
9 | class C:
|
||||||
|
|
|
||||||
|
::: src/foo.pyi:1:5
|
||||||
|
|
|
||||||
|
1 | def x(self, y: str): ...
|
||||||
|
| --------------- Signature of `B.x`
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `x`
|
||||||
|
--> src/bar.pyi:10:5
|
||||||
|
|
|
||||||
|
9 | class C:
|
||||||
|
10 | x = foo.x
|
||||||
|
| --------- `C.x` defined here
|
||||||
|
11 |
|
||||||
|
12 | class D(C):
|
||||||
|
13 | def x(self, y: int): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^ Definition is incompatible with `C.x`
|
||||||
|
|
|
||||||
|
::: src/foo.pyi:1:5
|
||||||
|
|
|
||||||
|
1 | def x(self, y: str): ...
|
||||||
|
| --------------- Signature of `C.x`
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Fully qualified names are used in diagnostics where appropriate
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## a.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class A:
|
||||||
|
2 | def foo(self, x): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## b.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | import a
|
||||||
|
2 |
|
||||||
|
3 | class A(a.A):
|
||||||
|
4 | def foo(self, y): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `foo`
|
||||||
|
--> src/b.pyi:4:9
|
||||||
|
|
|
||||||
|
3 | class A(a.A):
|
||||||
|
4 | def foo(self, y): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^ Definition is incompatible with `a.A.foo`
|
||||||
|
|
|
||||||
|
::: src/a.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class A:
|
||||||
|
2 | def foo(self, x): ...
|
||||||
|
| ------------ `a.A.foo` defined here
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,331 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Method parameters
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self, x: int, /): ...
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
5 | def method(self, x: int, /): ... # fine
|
||||||
|
6 |
|
||||||
|
7 | class Sub2(Super):
|
||||||
|
8 | def method(self, x: object, /): ... # fine: `method` still accepts any argument of type `int`
|
||||||
|
9 |
|
||||||
|
10 | class Sub4(Super):
|
||||||
|
11 | def method(self, x: int | str, /): ... # fine
|
||||||
|
12 |
|
||||||
|
13 | class Sub5(Super):
|
||||||
|
14 | def method(self, x: int): ... # fine: `x` can still be passed positionally
|
||||||
|
15 |
|
||||||
|
16 | class Sub6(Super):
|
||||||
|
17 | # fine: `method()` can still be called with just a single argument
|
||||||
|
18 | def method(self, x: int, *args): ...
|
||||||
|
19 |
|
||||||
|
20 | class Sub7(Super):
|
||||||
|
21 | def method(self, x: int, **kwargs): ... # fine
|
||||||
|
22 |
|
||||||
|
23 | class Sub8(Super):
|
||||||
|
24 | def method(self, x: int, *args, **kwargs): ... # fine
|
||||||
|
25 |
|
||||||
|
26 | class Sub9(Super):
|
||||||
|
27 | def method(self, x: int, extra_positional_arg=42, /): ... # fine
|
||||||
|
28 |
|
||||||
|
29 | class Sub10(Super):
|
||||||
|
30 | def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine
|
||||||
|
31 |
|
||||||
|
32 | class Sub11(Super):
|
||||||
|
33 | def method(self, x: int, *, extra_kw_only_arg=42): ... # fine
|
||||||
|
34 |
|
||||||
|
35 | class Sub12(Super):
|
||||||
|
36 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
37 | # (the method can no longer be passed any arguments!)
|
||||||
|
38 | def method(self, /): ... # error: [invalid-method-override]
|
||||||
|
39 |
|
||||||
|
40 | class Sub13(Super):
|
||||||
|
41 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
42 | # (the method can no longer be passed exactly one argument!)
|
||||||
|
43 | def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||||
|
44 |
|
||||||
|
45 | class Sub14(Super):
|
||||||
|
46 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
47 | # (x can no longer be passed positionally!)
|
||||||
|
48 | def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||||
|
49 |
|
||||||
|
50 | class Sub15(Super):
|
||||||
|
51 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
52 | # (x can no longer be passed any integer -- it now requires a bool!)
|
||||||
|
53 | def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||||
|
54 |
|
||||||
|
55 | class Super2:
|
||||||
|
56 | def method2(self, x): ...
|
||||||
|
57 |
|
||||||
|
58 | class Sub16(Super2):
|
||||||
|
59 | def method2(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
60 |
|
||||||
|
61 | class Sub17(Super2):
|
||||||
|
62 | def method2(self, *, x): ... # error: [invalid-method-override]
|
||||||
|
63 |
|
||||||
|
64 | class Super3:
|
||||||
|
65 | def method3(self, *, x): ...
|
||||||
|
66 |
|
||||||
|
67 | class Sub18(Super3):
|
||||||
|
68 | def method3(self, x): ... # fine: `x` can still be used as a keyword argument
|
||||||
|
69 |
|
||||||
|
70 | class Sub19(Super3):
|
||||||
|
71 | def method3(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
72 |
|
||||||
|
73 | class Super4:
|
||||||
|
74 | def method(self, *args: int, **kwargs: str): ...
|
||||||
|
75 |
|
||||||
|
76 | class Sub20(Super4):
|
||||||
|
77 | def method(self, *args: object, **kwargs: object): ... # fine
|
||||||
|
78 |
|
||||||
|
79 | class Sub21(Super4):
|
||||||
|
80 | def method(self, *args): ... # error: [invalid-method-override]
|
||||||
|
81 |
|
||||||
|
82 | class Sub22(Super4):
|
||||||
|
83 | def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
84 |
|
||||||
|
85 | class Sub23(Super4):
|
||||||
|
86 | def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:38:9
|
||||||
|
|
|
||||||
|
36 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
37 | # (the method can no longer be passed any arguments!)
|
||||||
|
38 | def method(self, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
39 |
|
||||||
|
40 | class Sub13(Super):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self, x: int, /): ...
|
||||||
|
| ----------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:43:9
|
||||||
|
|
|
||||||
|
41 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
42 | # (the method can no longer be passed exactly one argument!)
|
||||||
|
43 | def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
44 |
|
||||||
|
45 | class Sub14(Super):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self, x: int, /): ...
|
||||||
|
| ----------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:48:9
|
||||||
|
|
|
||||||
|
46 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
47 | # (x can no longer be passed positionally!)
|
||||||
|
48 | def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
49 |
|
||||||
|
50 | class Sub15(Super):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self, x: int, /): ...
|
||||||
|
| ----------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:53:9
|
||||||
|
|
|
||||||
|
51 | # Some calls permitted by the superclass are now no longer allowed
|
||||||
|
52 | # (x can no longer be passed any integer -- it now requires a bool!)
|
||||||
|
53 | def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
54 |
|
||||||
|
55 | class Super2:
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self, x: int, /): ...
|
||||||
|
| ----------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method2`
|
||||||
|
--> src/mdtest_snippet.pyi:56:9
|
||||||
|
|
|
||||||
|
55 | class Super2:
|
||||||
|
56 | def method2(self, x): ...
|
||||||
|
| ---------------- `Super2.method2` defined here
|
||||||
|
57 |
|
||||||
|
58 | class Sub16(Super2):
|
||||||
|
59 | def method2(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super2.method2`
|
||||||
|
60 |
|
||||||
|
61 | class Sub17(Super2):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method2`
|
||||||
|
--> src/mdtest_snippet.pyi:62:9
|
||||||
|
|
|
||||||
|
61 | class Sub17(Super2):
|
||||||
|
62 | def method2(self, *, x): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super2.method2`
|
||||||
|
63 |
|
||||||
|
64 | class Super3:
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:56:9
|
||||||
|
|
|
||||||
|
55 | class Super2:
|
||||||
|
56 | def method2(self, x): ...
|
||||||
|
| ---------------- `Super2.method2` defined here
|
||||||
|
57 |
|
||||||
|
58 | class Sub16(Super2):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method3`
|
||||||
|
--> src/mdtest_snippet.pyi:71:9
|
||||||
|
|
|
||||||
|
70 | class Sub19(Super3):
|
||||||
|
71 | def method3(self, x, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super3.method3`
|
||||||
|
72 |
|
||||||
|
73 | class Super4:
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:65:9
|
||||||
|
|
|
||||||
|
64 | class Super3:
|
||||||
|
65 | def method3(self, *, x): ...
|
||||||
|
| ------------------- `Super3.method3` defined here
|
||||||
|
66 |
|
||||||
|
67 | class Sub18(Super3):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:80:9
|
||||||
|
|
|
||||||
|
79 | class Sub21(Super4):
|
||||||
|
80 | def method(self, *args): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||||
|
81 |
|
||||||
|
82 | class Sub22(Super4):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:74:9
|
||||||
|
|
|
||||||
|
73 | class Super4:
|
||||||
|
74 | def method(self, *args: int, **kwargs: str): ...
|
||||||
|
| --------------------------------------- `Super4.method` defined here
|
||||||
|
75 |
|
||||||
|
76 | class Sub20(Super4):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:83:9
|
||||||
|
|
|
||||||
|
82 | class Sub22(Super4):
|
||||||
|
83 | def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||||
|
84 |
|
||||||
|
85 | class Sub23(Super4):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:74:9
|
||||||
|
|
|
||||||
|
73 | class Super4:
|
||||||
|
74 | def method(self, *args: int, **kwargs: str): ...
|
||||||
|
| --------------------------------------- `Super4.method` defined here
|
||||||
|
75 |
|
||||||
|
76 | class Sub20(Super4):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:86:9
|
||||||
|
|
|
||||||
|
85 | class Sub23(Super4):
|
||||||
|
86 | def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:74:9
|
||||||
|
|
|
||||||
|
73 | class Super4:
|
||||||
|
74 | def method(self, *args: int, **kwargs: str): ...
|
||||||
|
| --------------------------------------- `Super4.method` defined here
|
||||||
|
75 |
|
||||||
|
76 | class Sub20(Super4):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Method return types
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self) -> int: ...
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
5 | def method(self) -> int: ... # fine
|
||||||
|
6 |
|
||||||
|
7 | class Sub2(Super):
|
||||||
|
8 | def method(self) -> bool: ... # fine: `bool` is a subtype of `int`
|
||||||
|
9 |
|
||||||
|
10 | class Sub3(Super):
|
||||||
|
11 | def method(self) -> object: ... # error: [invalid-method-override]
|
||||||
|
12 |
|
||||||
|
13 | class Sub4(Super):
|
||||||
|
14 | def method(self) -> str: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:11:9
|
||||||
|
|
|
||||||
|
10 | class Sub3(Super):
|
||||||
|
11 | def method(self) -> object: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
12 |
|
||||||
|
13 | class Sub4(Super):
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self) -> int: ...
|
||||||
|
| ------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/mdtest_snippet.pyi:14:9
|
||||||
|
|
|
||||||
|
13 | class Sub4(Super):
|
||||||
|
14 | def method(self) -> str: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||||
|
|
|
||||||
|
::: src/mdtest_snippet.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class Super:
|
||||||
|
2 | def method(self) -> int: ...
|
||||||
|
| ------------------- `Super.method` defined here
|
||||||
|
3 |
|
||||||
|
4 | class Sub1(Super):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - Synthesized methods
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from dataclasses import dataclass
|
||||||
|
2 | from typing import NamedTuple
|
||||||
|
3 |
|
||||||
|
4 | @dataclass(order=True)
|
||||||
|
5 | class Foo:
|
||||||
|
6 | x: int
|
||||||
|
7 |
|
||||||
|
8 | class Bar(Foo):
|
||||||
|
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||||
|
10 |
|
||||||
|
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||||
|
12 | # generated that is incompatible with the generated `__lt__` method on the superclass.
|
||||||
|
13 | # We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
|
||||||
|
14 | # be `invalid-method-override` since we'd emit it on the class definition rather than
|
||||||
|
15 | # on any method definition. Note also that no other type checker complains about this
|
||||||
|
16 | # as of 2025-11-21.
|
||||||
|
17 | @dataclass(order=True)
|
||||||
|
18 | class Bar2(Foo):
|
||||||
|
19 | y: str
|
||||||
|
20 |
|
||||||
|
21 | # TODO: Although this class does not override any methods of `Foo`, the design of the
|
||||||
|
22 | # `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
|
||||||
|
23 | # Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
|
||||||
|
24 | # expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
|
||||||
|
25 | # and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
|
||||||
|
26 | # be compared with instances of subclasses of `Foo`).
|
||||||
|
27 | #
|
||||||
|
28 | # Many users would probably like their type checkers to alert them to cases where instances
|
||||||
|
29 | # of subclasses cannot be substituted for instances of superclasses, as this violates many
|
||||||
|
30 | # assumptions a type checker will make and makes it likely that a type checker will fail to
|
||||||
|
31 | # catch type errors elsewhere in the user's code. We could therefore consider treating all
|
||||||
|
32 | # `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
|
||||||
|
33 | # this probably shouldn't be reported with the same error code as Liskov violations, since
|
||||||
|
34 | # the error does not stem from any method signatures written by the user. The example is
|
||||||
|
35 | # only included here for completeness.
|
||||||
|
36 | #
|
||||||
|
37 | # Note that no other type checker catches this error as of 2025-11-21.
|
||||||
|
38 | class Bar3(Foo): ...
|
||||||
|
39 |
|
||||||
|
40 | class Eggs:
|
||||||
|
41 | def __lt__(self, other: Eggs) -> bool: ...
|
||||||
|
42 |
|
||||||
|
43 | # TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
|
||||||
|
44 | # We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
|
||||||
|
45 | # diagnostic here but pyright and pyrefly do not.
|
||||||
|
46 | @dataclass(order=True)
|
||||||
|
47 | class Ham(Eggs):
|
||||||
|
48 | x: int
|
||||||
|
49 |
|
||||||
|
50 | class Baz(NamedTuple):
|
||||||
|
51 | x: int
|
||||||
|
52 |
|
||||||
|
53 | class Spam(Baz):
|
||||||
|
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `__lt__`
|
||||||
|
--> src/mdtest_snippet.pyi:9:9
|
||||||
|
|
|
||||||
|
8 | class Bar(Foo):
|
||||||
|
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Foo.__lt__`
|
||||||
|
10 |
|
||||||
|
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: `Foo.__lt__` is a generated method created because `Foo` is a dataclass
|
||||||
|
--> src/mdtest_snippet.pyi:5:7
|
||||||
|
|
|
||||||
|
4 | @dataclass(order=True)
|
||||||
|
5 | class Foo:
|
||||||
|
| ^^^ Definition of `Foo`
|
||||||
|
6 | x: int
|
||||||
|
|
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `_asdict`
|
||||||
|
--> src/mdtest_snippet.pyi:54:9
|
||||||
|
|
|
||||||
|
53 | class Spam(Baz):
|
||||||
|
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Baz._asdict`
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: `Baz._asdict` is a generated method created because `Baz` inherits from `typing.NamedTuple`
|
||||||
|
--> src/mdtest_snippet.pyi:50:7
|
||||||
|
|
|
||||||
|
48 | x: int
|
||||||
|
49 |
|
||||||
|
50 | class Baz(NamedTuple):
|
||||||
|
| ^^^^^^^^^^^^^^^ Definition of `Baz`
|
||||||
|
51 | x: int
|
||||||
|
|
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: liskov.md - The Liskov Substitution Principle - The entire class hierarchy is checked
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## stub.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing import Any
|
||||||
|
2 |
|
||||||
|
3 | class Grandparent:
|
||||||
|
4 | def method(self, x: int) -> None: ...
|
||||||
|
5 |
|
||||||
|
6 | class Parent(Grandparent):
|
||||||
|
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
8 |
|
||||||
|
9 | class Child(Parent):
|
||||||
|
10 | # compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||||
|
11 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
12 |
|
||||||
|
13 | class OtherChild(Parent):
|
||||||
|
14 | # compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||||
|
15 | def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||||
|
16 |
|
||||||
|
17 | class GradualParent(Grandparent):
|
||||||
|
18 | def method(self, x: Any) -> None: ...
|
||||||
|
19 |
|
||||||
|
20 | class ThirdChild(GradualParent):
|
||||||
|
21 | # `GradualParent.method` is compatible with the signature of `Grandparent.method`,
|
||||||
|
22 | # and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||||
|
23 | # but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||||
|
24 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
## other_stub.pyi
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class A:
|
||||||
|
2 | def get(self, default): ...
|
||||||
|
3 |
|
||||||
|
4 | class B(A):
|
||||||
|
5 | def get(self, default, /): ... # error: [invalid-method-override]
|
||||||
|
6 |
|
||||||
|
7 | get = 56
|
||||||
|
8 |
|
||||||
|
9 | class C(B):
|
||||||
|
10 | # `get` appears in the symbol table of `C`,
|
||||||
|
11 | # but that doesn't confuse our diagnostic...
|
||||||
|
12 | foo = get
|
||||||
|
13 |
|
||||||
|
14 | class D(C):
|
||||||
|
15 | # compatible with `C.get` and `B.get`, but not with `A.get`
|
||||||
|
16 | def get(self, my_default): ... # error: [invalid-method-override]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/stub.pyi:4:9
|
||||||
|
|
|
||||||
|
3 | class Grandparent:
|
||||||
|
4 | def method(self, x: int) -> None: ...
|
||||||
|
| ---------------------------- `Grandparent.method` defined here
|
||||||
|
5 |
|
||||||
|
6 | class Parent(Grandparent):
|
||||||
|
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||||
|
8 |
|
||||||
|
9 | class Child(Parent):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/stub.pyi:11:9
|
||||||
|
|
|
||||||
|
9 | class Child(Parent):
|
||||||
|
10 | # compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||||
|
11 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||||
|
12 |
|
||||||
|
13 | class OtherChild(Parent):
|
||||||
|
|
|
||||||
|
::: src/stub.pyi:4:9
|
||||||
|
|
|
||||||
|
3 | class Grandparent:
|
||||||
|
4 | def method(self, x: int) -> None: ...
|
||||||
|
| ---------------------------- `Grandparent.method` defined here
|
||||||
|
5 |
|
||||||
|
6 | class Parent(Grandparent):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/stub.pyi:15:9
|
||||||
|
|
|
||||||
|
13 | class OtherChild(Parent):
|
||||||
|
14 | # compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||||
|
15 | def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.method`
|
||||||
|
16 |
|
||||||
|
17 | class GradualParent(Grandparent):
|
||||||
|
|
|
||||||
|
::: src/stub.pyi:7:9
|
||||||
|
|
|
||||||
|
6 | class Parent(Grandparent):
|
||||||
|
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
| ---------------------------- `Parent.method` defined here
|
||||||
|
8 |
|
||||||
|
9 | class Child(Parent):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `method`
|
||||||
|
--> src/stub.pyi:24:9
|
||||||
|
|
|
||||||
|
22 | # and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||||
|
23 | # but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||||
|
24 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||||
|
|
|
||||||
|
::: src/stub.pyi:4:9
|
||||||
|
|
|
||||||
|
3 | class Grandparent:
|
||||||
|
4 | def method(self, x: int) -> None: ...
|
||||||
|
| ---------------------------- `Grandparent.method` defined here
|
||||||
|
5 |
|
||||||
|
6 | class Parent(Grandparent):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `get`
|
||||||
|
--> src/other_stub.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class A:
|
||||||
|
2 | def get(self, default): ...
|
||||||
|
| ------------------ `A.get` defined here
|
||||||
|
3 |
|
||||||
|
4 | class B(A):
|
||||||
|
5 | def get(self, default, /): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `A.get`
|
||||||
|
6 |
|
||||||
|
7 | get = 56
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-method-override]: Invalid override of method `get`
|
||||||
|
--> src/other_stub.pyi:16:9
|
||||||
|
|
|
||||||
|
14 | class D(C):
|
||||||
|
15 | # compatible with `C.get` and `B.get`, but not with `A.get`
|
||||||
|
16 | def get(self, my_default): ... # error: [invalid-method-override]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `A.get`
|
||||||
|
|
|
||||||
|
::: src/other_stub.pyi:2:9
|
||||||
|
|
|
||||||
|
1 | class A:
|
||||||
|
2 | def get(self, default): ...
|
||||||
|
| ------------------ `A.get` defined here
|
||||||
|
3 |
|
||||||
|
4 | class B(A):
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -16,21 +16,53 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||||
2 | __bool__: None = None
|
2 | __bool__: None = None
|
||||||
3 |
|
3 |
|
||||||
4 | class A:
|
4 | class A:
|
||||||
5 | def __eq__(self, other) -> NotBoolable:
|
5 | # error: [invalid-method-override]
|
||||||
6 | return NotBoolable()
|
6 | def __eq__(self, other) -> NotBoolable:
|
||||||
7 |
|
7 | return NotBoolable()
|
||||||
8 | # error: [unsupported-bool-conversion]
|
8 |
|
||||||
9 | (A(),) == (A(),)
|
9 | # error: [unsupported-bool-conversion]
|
||||||
|
10 | (A(),) == (A(),)
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable`
|
error[invalid-method-override]: Invalid override of method `__eq__`
|
||||||
--> src/mdtest_snippet.py:9:1
|
--> src/mdtest_snippet.py:6:9
|
||||||
|
|
|
|
||||||
8 | # error: [unsupported-bool-conversion]
|
4 | class A:
|
||||||
9 | (A(),) == (A(),)
|
5 | # error: [invalid-method-override]
|
||||||
|
6 | def __eq__(self, other) -> NotBoolable:
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `object.__eq__`
|
||||||
|
7 | return NotBoolable()
|
||||||
|
|
|
||||||
|
::: stdlib/builtins.pyi:142:9
|
||||||
|
|
|
||||||
|
140 | def __setattr__(self, name: str, value: Any, /) -> None: ...
|
||||||
|
141 | def __delattr__(self, name: str, /) -> None: ...
|
||||||
|
142 | def __eq__(self, value: object, /) -> bool: ...
|
||||||
|
| -------------------------------------- `object.__eq__` defined here
|
||||||
|
143 | def __ne__(self, value: object, /) -> bool: ...
|
||||||
|
144 | def __str__(self) -> str: ... # noqa: Y029
|
||||||
|
|
|
||||||
|
info: This violates the Liskov Substitution Principle
|
||||||
|
help: It is recommended for `__eq__` to work with arbitrary objects, for example:
|
||||||
|
help
|
||||||
|
help: def __eq__(self, other: object) -> bool:
|
||||||
|
help: if not isinstance(other, A):
|
||||||
|
help: return False
|
||||||
|
help: return <logic to compare two `A` instances>
|
||||||
|
help
|
||||||
|
info: rule `invalid-method-override` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable`
|
||||||
|
--> src/mdtest_snippet.py:10:1
|
||||||
|
|
|
||||||
|
9 | # error: [unsupported-bool-conversion]
|
||||||
|
10 | (A(),) == (A(),)
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
info: `__bool__` on `NotBoolable` must be callable
|
info: `__bool__` on `NotBoolable` must be callable
|
||||||
|
|
|
||||||
|
|
@ -737,6 +737,10 @@ impl DefinitionKind<'_> {
|
||||||
matches!(self, DefinitionKind::Assignment(_))
|
matches!(self, DefinitionKind::Assignment(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_function_def(&self) -> bool {
|
||||||
|
matches!(self, DefinitionKind::Function(_))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`TextRange`] of the definition target.
|
/// Returns the [`TextRange`] of the definition target.
|
||||||
///
|
///
|
||||||
/// A definition target would mainly be the node representing the place being defined i.e.,
|
/// A definition target would mainly be the node representing the place being defined i.e.,
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ mod generics;
|
||||||
pub mod ide_support;
|
pub mod ide_support;
|
||||||
mod infer;
|
mod infer;
|
||||||
mod instance;
|
mod instance;
|
||||||
|
mod liskov;
|
||||||
mod member;
|
mod member;
|
||||||
mod mro;
|
mod mro;
|
||||||
mod narrow;
|
mod narrow;
|
||||||
|
|
@ -1104,6 +1105,13 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn as_bound_method(self) -> Option<BoundMethodType<'db>> {
|
||||||
|
match self {
|
||||||
|
Type::BoundMethod(bound_method) => Some(bound_method),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) const fn expect_class_literal(self) -> ClassLiteral<'db> {
|
pub(crate) const fn expect_class_literal(self) -> ClassLiteral<'db> {
|
||||||
self.as_class_literal()
|
self.as_class_literal()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::{LazyLock, Mutex};
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
use super::TypeVarVariance;
|
use super::TypeVarVariance;
|
||||||
|
|
@ -10,7 +11,7 @@ use super::{
|
||||||
use crate::module_resolver::KnownModule;
|
use crate::module_resolver::KnownModule;
|
||||||
use crate::place::TypeOrigin;
|
use crate::place::TypeOrigin;
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||||
use crate::semantic_index::scope::{NodeWithScopeKind, Scope};
|
use crate::semantic_index::scope::{NodeWithScopeKind, Scope, ScopeKind};
|
||||||
use crate::semantic_index::symbol::Symbol;
|
use crate::semantic_index::symbol::Symbol;
|
||||||
use crate::semantic_index::{
|
use crate::semantic_index::{
|
||||||
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
|
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
|
||||||
|
|
@ -3649,6 +3650,10 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.unwrap_or_else(|| class_name.end()),
|
.unwrap_or_else(|| class_name.end()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn qualified_name(self, db: &'db dyn Db) -> QualifiedClassName<'db> {
|
||||||
|
QualifiedClassName { db, class: self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<ClassLiteral<'db>> for Type<'db> {
|
impl<'db> From<ClassLiteral<'db>> for Type<'db> {
|
||||||
|
|
@ -3783,6 +3788,74 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// N.B. It would be incorrect to derive `Eq`, `PartialEq`, or `Hash` for this struct,
|
||||||
|
// because two `QualifiedClassName` instances might refer to different classes but
|
||||||
|
// have the same components. You'd expect them to compare equal, but they'd compare
|
||||||
|
// unequal if `PartialEq`/`Eq` were naively derived.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(super) struct QualifiedClassName<'db> {
|
||||||
|
db: &'db dyn Db,
|
||||||
|
class: ClassLiteral<'db>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QualifiedClassName<'_> {
|
||||||
|
/// Returns the components of the qualified name of this class, excluding this class itself.
|
||||||
|
///
|
||||||
|
/// For example, calling this method on a class `C` in the module `a.b` would return
|
||||||
|
/// `["a", "b"]`. Calling this method on a class `D` inside the namespace of a method
|
||||||
|
/// `m` inside the namespace of a class `C` in the module `a.b` would return
|
||||||
|
/// `["a", "b", "C", "<locals of function 'm'>"]`.
|
||||||
|
pub(super) fn components_excluding_self(&self) -> Vec<String> {
|
||||||
|
let body_scope = self.class.body_scope(self.db);
|
||||||
|
let file = body_scope.file(self.db);
|
||||||
|
let module_ast = parsed_module(self.db, file).load(self.db);
|
||||||
|
let index = semantic_index(self.db, file);
|
||||||
|
let file_scope_id = body_scope.file_scope_id(self.db);
|
||||||
|
|
||||||
|
let mut name_parts = vec![];
|
||||||
|
|
||||||
|
// Skips itself
|
||||||
|
for (_, ancestor_scope) in index.ancestor_scopes(file_scope_id).skip(1) {
|
||||||
|
let node = ancestor_scope.node();
|
||||||
|
|
||||||
|
match ancestor_scope.kind() {
|
||||||
|
ScopeKind::Class => {
|
||||||
|
if let Some(class_def) = node.as_class() {
|
||||||
|
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScopeKind::Function => {
|
||||||
|
if let Some(function_def) = node.as_function() {
|
||||||
|
name_parts.push(format!(
|
||||||
|
"<locals of function '{}'>",
|
||||||
|
function_def.node(&module_ast).name.as_str()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(module) = file_to_module(self.db, file) {
|
||||||
|
let module_name = module.name(self.db);
|
||||||
|
name_parts.push(module_name.as_str().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
name_parts.reverse();
|
||||||
|
name_parts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for QualifiedClassName<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for parent in self.components_excluding_self() {
|
||||||
|
f.write_str(&parent)?;
|
||||||
|
f.write_char('.')?;
|
||||||
|
}
|
||||||
|
f.write_str(self.class.name(self.db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||||
pub(super) enum InheritanceCycle {
|
pub(super) enum InheritanceCycle {
|
||||||
/// The class is cyclically defined and is a participant in the cycle.
|
/// The class is cyclically defined and is a participant in the cycle.
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,13 @@ use crate::diagnostic::format_enumeration;
|
||||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||||
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
||||||
use crate::semantic_index::{global_scope, place_table};
|
use crate::semantic_index::{global_scope, place_table, use_def_map};
|
||||||
use crate::suppression::FileSuppressionId;
|
use crate::suppression::FileSuppressionId;
|
||||||
use crate::types::KnownInstanceType;
|
use crate::types::KnownInstanceType;
|
||||||
use crate::types::call::CallError;
|
use crate::types::call::CallError;
|
||||||
use crate::types::class::{DisjointBase, DisjointBaseKind, Field};
|
use crate::types::class::{DisjointBase, DisjointBaseKind, Field};
|
||||||
use crate::types::function::KnownFunction;
|
use crate::types::function::{FunctionType, KnownFunction};
|
||||||
|
use crate::types::liskov::{MethodKind, SynthesizedMethodKind};
|
||||||
use crate::types::string_annotation::{
|
use crate::types::string_annotation::{
|
||||||
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||||
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||||
|
|
@ -28,15 +29,18 @@ use crate::types::{
|
||||||
};
|
};
|
||||||
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
|
use ruff_db::{
|
||||||
use ruff_db::source::source_text;
|
diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity},
|
||||||
|
parsed::parsed_module,
|
||||||
|
source::source_text,
|
||||||
|
};
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
use ruff_python_ast::parenthesize::parentheses_iterator;
|
use ruff_python_ast::parenthesize::parentheses_iterator;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_python_trivia::CommentRanges;
|
use ruff_python_trivia::CommentRanges;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::{self, Formatter};
|
||||||
|
|
||||||
/// Registers all known type check lints.
|
/// Registers all known type check lints.
|
||||||
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||||
|
|
@ -108,6 +112,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||||
registry.register_lint(&REDUNDANT_CAST);
|
registry.register_lint(&REDUNDANT_CAST);
|
||||||
registry.register_lint(&UNRESOLVED_GLOBAL);
|
registry.register_lint(&UNRESOLVED_GLOBAL);
|
||||||
registry.register_lint(&MISSING_TYPED_DICT_KEY);
|
registry.register_lint(&MISSING_TYPED_DICT_KEY);
|
||||||
|
registry.register_lint(&INVALID_METHOD_OVERRIDE);
|
||||||
|
|
||||||
// String annotations
|
// String annotations
|
||||||
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
|
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
|
||||||
|
|
@ -1934,6 +1939,84 @@ declare_lint! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare_lint! {
|
||||||
|
/// ## What it does
|
||||||
|
/// Detects method overrides that violate the [Liskov Substitution Principle] ("LSP").
|
||||||
|
///
|
||||||
|
/// The LSP states that an instance of a subtype should be substitutable for an instance of its supertype.
|
||||||
|
/// Applied to Python, this means:
|
||||||
|
/// 1. All argument combinations a superclass method accepts
|
||||||
|
/// must also be accepted by an overriding subclass method.
|
||||||
|
/// 2. The return type of an overriding subclass method must be a subtype
|
||||||
|
/// of the return type of the superclass method.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Violating the Liskov Substitution Principle will lead to many of ty's assumptions and
|
||||||
|
/// inferences being incorrect, which will mean that it will fail to catch many possible
|
||||||
|
/// type errors in your code.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// class Super:
|
||||||
|
/// def method(self, x) -> int:
|
||||||
|
/// return 42
|
||||||
|
///
|
||||||
|
/// class Sub(Super):
|
||||||
|
/// # Liskov violation: `str` is not a subtype of `int`,
|
||||||
|
/// # but the supertype method promises to return an `int`.
|
||||||
|
/// def method(self, x) -> str: # error: [invalid-override]
|
||||||
|
/// return "foo"
|
||||||
|
///
|
||||||
|
/// def accepts_super(s: Super) -> int:
|
||||||
|
/// return s.method(x=42)
|
||||||
|
///
|
||||||
|
/// accepts_super(Sub()) # The result of this call is a string, but ty will infer
|
||||||
|
/// # it to be an `int` due to the violation of the Liskov Substitution Principle.
|
||||||
|
///
|
||||||
|
/// class Sub2(Super):
|
||||||
|
/// # Liskov violation: the superclass method can be called with a `x=`
|
||||||
|
/// # keyword argument, but the subclass method does not accept it.
|
||||||
|
/// def method(self, y) -> int: # error: [invalid-override]
|
||||||
|
/// return 42
|
||||||
|
///
|
||||||
|
/// accepts_super(Sub2()) # TypeError at runtime: method() got an unexpected keyword argument 'x'
|
||||||
|
/// # ty cannot catch this error due to the violation of the Liskov Substitution Principle.
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Common issues
|
||||||
|
///
|
||||||
|
/// ### Why does ty complain about my `__eq__` method?
|
||||||
|
///
|
||||||
|
/// `__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary
|
||||||
|
/// objects as their second argument, for example:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// class A:
|
||||||
|
/// x: int
|
||||||
|
///
|
||||||
|
/// def __eq__(self, other: object) -> bool:
|
||||||
|
/// # gracefully handle an object of an unexpected type
|
||||||
|
/// # without raising an exception
|
||||||
|
/// if not isinstance(other, A):
|
||||||
|
/// return False
|
||||||
|
/// return self.x == other.x
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If `A.__eq__` here were annotated as only accepting `A` instances for its second argument,
|
||||||
|
/// it would imply that you wouldn't be able to use `==` between instances of `A` and
|
||||||
|
/// instances of unrelated classes without an exception possibly being raised. While some
|
||||||
|
/// classes in Python do indeed behave this way, the strongly held convention is that it should
|
||||||
|
/// be avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__`
|
||||||
|
/// and `__ne__` methods accept `object` as their second argument.
|
||||||
|
///
|
||||||
|
/// [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
|
||||||
|
pub(crate) static INVALID_METHOD_OVERRIDE = {
|
||||||
|
summary: "detects method definitions that violate the Liskov Substitution Principle",
|
||||||
|
status: LintStatus::stable("0.0.1-alpha.20"),
|
||||||
|
default_level: Level::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A collection of type check diagnostics.
|
/// A collection of type check diagnostics.
|
||||||
#[derive(Default, Eq, PartialEq, get_size2::GetSize)]
|
#[derive(Default, Eq, PartialEq, get_size2::GetSize)]
|
||||||
pub struct TypeCheckDiagnostics {
|
pub struct TypeCheckDiagnostics {
|
||||||
|
|
@ -3372,6 +3455,173 @@ pub(crate) fn report_rebound_typevar<'db>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I tried refactoring this function to placate Clippy,
|
||||||
|
// but it did not improve readability! -- AW.
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
|
pub(super) fn report_invalid_method_override<'db>(
|
||||||
|
context: &InferContext<'db, '_>,
|
||||||
|
member: &str,
|
||||||
|
subclass: ClassType<'db>,
|
||||||
|
subclass_definition: Definition<'db>,
|
||||||
|
subclass_function: FunctionType<'db>,
|
||||||
|
superclass: ClassType<'db>,
|
||||||
|
superclass_type: Type<'db>,
|
||||||
|
superclass_method_kind: MethodKind,
|
||||||
|
) {
|
||||||
|
let db = context.db();
|
||||||
|
|
||||||
|
let signature_span = |function: FunctionType<'db>| {
|
||||||
|
function
|
||||||
|
.literal(db)
|
||||||
|
.last_definition(db)
|
||||||
|
.spans(db)
|
||||||
|
.map(|spans| spans.signature)
|
||||||
|
};
|
||||||
|
|
||||||
|
let subclass_definition_kind = subclass_definition.kind(db);
|
||||||
|
let subclass_definition_signature_span = signature_span(subclass_function);
|
||||||
|
|
||||||
|
// If the function was originally defined elsewhere and simply assigned
|
||||||
|
// in the body of the class here, we cannot use the range associated with the `FunctionType`
|
||||||
|
let diagnostic_range = if subclass_definition_kind.is_function_def() {
|
||||||
|
subclass_definition_signature_span
|
||||||
|
.as_ref()
|
||||||
|
.and_then(Span::range)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
subclass_function
|
||||||
|
.node(db, context.file(), context.module())
|
||||||
|
.range
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
subclass_definition.full_range(db, context.module()).range()
|
||||||
|
};
|
||||||
|
|
||||||
|
let class_name = subclass.name(db);
|
||||||
|
let superclass_name = superclass.name(db);
|
||||||
|
|
||||||
|
let overridden_method = if class_name == superclass_name {
|
||||||
|
format!(
|
||||||
|
"{superclass}.{member}",
|
||||||
|
superclass = superclass.class_literal(db).0.qualified_name(db),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{superclass_name}.{member}")
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(builder) = context.report_lint(&INVALID_METHOD_OVERRIDE, diagnostic_range) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diagnostic =
|
||||||
|
builder.into_diagnostic(format_args!("Invalid override of method `{member}`"));
|
||||||
|
|
||||||
|
diagnostic.set_primary_message(format_args!(
|
||||||
|
"Definition is incompatible with `{overridden_method}`"
|
||||||
|
));
|
||||||
|
|
||||||
|
diagnostic.info("This violates the Liskov Substitution Principle");
|
||||||
|
|
||||||
|
if !subclass_definition_kind.is_function_def()
|
||||||
|
&& let Some(span) = subclass_definition_signature_span
|
||||||
|
{
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(span)
|
||||||
|
.message(format_args!("Signature of `{class_name}.{member}`")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let superclass_scope = superclass.class_literal(db).0.body_scope(db);
|
||||||
|
|
||||||
|
match superclass_method_kind {
|
||||||
|
MethodKind::NotSynthesized => {
|
||||||
|
if let Some(superclass_symbol) = place_table(db, superclass_scope).symbol_id(member)
|
||||||
|
&& let Some(binding) = use_def_map(db, superclass_scope)
|
||||||
|
.end_of_scope_bindings(ScopedPlaceId::Symbol(superclass_symbol))
|
||||||
|
.next()
|
||||||
|
&& let Some(definition) = binding.binding.definition()
|
||||||
|
{
|
||||||
|
let definition_span = Span::from(
|
||||||
|
definition
|
||||||
|
.full_range(db, &parsed_module(db, superclass_scope.file(db)).load(db)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let superclass_function_span = superclass_type
|
||||||
|
.as_bound_method()
|
||||||
|
.and_then(|method| signature_span(method.function(db)));
|
||||||
|
|
||||||
|
let superclass_definition_kind = definition.kind(db);
|
||||||
|
|
||||||
|
let secondary_span = if superclass_definition_kind.is_function_def()
|
||||||
|
&& let Some(function_span) = superclass_function_span.clone()
|
||||||
|
{
|
||||||
|
function_span
|
||||||
|
} else {
|
||||||
|
definition_span
|
||||||
|
};
|
||||||
|
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(secondary_span.clone())
|
||||||
|
.message(format_args!("`{overridden_method}` defined here")),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !superclass_definition_kind.is_function_def()
|
||||||
|
&& let Some(function_span) = superclass_function_span
|
||||||
|
&& function_span != secondary_span
|
||||||
|
{
|
||||||
|
diagnostic.annotate(
|
||||||
|
Annotation::secondary(function_span)
|
||||||
|
.message(format_args!("Signature of `{overridden_method}`")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodKind::Synthesized(synthesized_kind) => {
|
||||||
|
let make_sub =
|
||||||
|
|message: fmt::Arguments| SubDiagnostic::new(SubDiagnosticSeverity::Info, message);
|
||||||
|
|
||||||
|
let mut sub = match synthesized_kind {
|
||||||
|
SynthesizedMethodKind::Dataclass => make_sub(format_args!(
|
||||||
|
"`{overridden_method}` is a generated method created because \
|
||||||
|
`{superclass_name}` is a dataclass"
|
||||||
|
)),
|
||||||
|
SynthesizedMethodKind::NamedTuple => make_sub(format_args!(
|
||||||
|
"`{overridden_method}` is a generated method created because \
|
||||||
|
`{superclass_name}` inherits from `typing.NamedTuple`"
|
||||||
|
)),
|
||||||
|
SynthesizedMethodKind::TypedDict => make_sub(format_args!(
|
||||||
|
"`{overridden_method}` is a generated method created because \
|
||||||
|
`{superclass_name}` is a `TypedDict`"
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
sub.annotate(
|
||||||
|
Annotation::primary(superclass.header_span(db))
|
||||||
|
.message(format_args!("Definition of `{superclass_name}`")),
|
||||||
|
);
|
||||||
|
diagnostic.sub(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if superclass.is_object(db) && matches!(member, "__eq__" | "__ne__") {
|
||||||
|
// Inspired by mypy's subdiagnostic at <https://github.com/python/mypy/blob/1b6ebb17b7fe64488a7b3c3b4b0187bb14fe331b/mypy/messages.py#L1307-L1318>
|
||||||
|
let eq_subdiagnostics = [
|
||||||
|
format_args!(
|
||||||
|
"It is recommended for `{member}` to work with arbitrary objects, for example:",
|
||||||
|
),
|
||||||
|
format_args!(""),
|
||||||
|
format_args!(" def {member}(self, other: object) -> bool:",),
|
||||||
|
format_args!(" if not isinstance(other, {class_name}):",),
|
||||||
|
format_args!(" return False"),
|
||||||
|
format_args!(" return <logic to compare two `{class_name}` instances>"),
|
||||||
|
format_args!(""),
|
||||||
|
];
|
||||||
|
|
||||||
|
for subdiag in eq_subdiagnostics {
|
||||||
|
diagnostic.help(subdiag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This function receives an unresolved `from foo import bar` import,
|
/// This function receives an unresolved `from foo import bar` import,
|
||||||
/// where `foo` can be resolved to a module but that module does not
|
/// where `foo` can be resolved to a module but that module does not
|
||||||
/// have a `bar` member or submodule.
|
/// have a `bar` member or submodule.
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::module_resolver::file_to_module;
|
|
||||||
use crate::semantic_index::{scope::ScopeKind, semantic_index};
|
|
||||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||||
use crate::types::function::{FunctionType, OverloadLiteral};
|
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization};
|
||||||
|
|
@ -27,7 +25,6 @@ use crate::types::{
|
||||||
MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType,
|
MaterializationKind, Protocol, ProtocolInstanceType, SpecialFormType, StringLiteralType,
|
||||||
SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor,
|
SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor,
|
||||||
};
|
};
|
||||||
use ruff_db::parsed::parsed_module;
|
|
||||||
|
|
||||||
/// Settings for displaying types and signatures
|
/// Settings for displaying types and signatures
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -342,8 +339,11 @@ impl<'db> AmbiguousClassCollector<'db> {
|
||||||
match value {
|
match value {
|
||||||
AmbiguityState::Unambiguous(existing) => {
|
AmbiguityState::Unambiguous(existing) => {
|
||||||
if *existing != class {
|
if *existing != class {
|
||||||
let qualified_name_components = class.qualified_name_components(db);
|
let qualified_name_components =
|
||||||
if existing.qualified_name_components(db) == qualified_name_components {
|
class.qualified_name(db).components_excluding_self();
|
||||||
|
if existing.qualified_name(db).components_excluding_self()
|
||||||
|
== qualified_name_components
|
||||||
|
{
|
||||||
*value = AmbiguityState::RequiresFileAndLineNumber;
|
*value = AmbiguityState::RequiresFileAndLineNumber;
|
||||||
} else {
|
} else {
|
||||||
*value = AmbiguityState::RequiresFullyQualifiedName {
|
*value = AmbiguityState::RequiresFullyQualifiedName {
|
||||||
|
|
@ -358,7 +358,8 @@ impl<'db> AmbiguousClassCollector<'db> {
|
||||||
qualified_name_components,
|
qualified_name_components,
|
||||||
} => {
|
} => {
|
||||||
if *existing != class {
|
if *existing != class {
|
||||||
let new_components = class.qualified_name_components(db);
|
let new_components =
|
||||||
|
class.qualified_name(db).components_excluding_self();
|
||||||
if *qualified_name_components == new_components {
|
if *qualified_name_components == new_components {
|
||||||
*value = AmbiguityState::RequiresFileAndLineNumber;
|
*value = AmbiguityState::RequiresFileAndLineNumber;
|
||||||
}
|
}
|
||||||
|
|
@ -505,52 +506,6 @@ impl<'db> ClassLiteral<'db> {
|
||||||
settings,
|
settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the components of the qualified name of this class, excluding this class itself.
|
|
||||||
///
|
|
||||||
/// For example, calling this method on a class `C` in the module `a.b` would return
|
|
||||||
/// `["a", "b"]`. Calling this method on a class `D` inside the namespace of a method
|
|
||||||
/// `m` inside the namespace of a class `C` in the module `a.b` would return
|
|
||||||
/// `["a", "b", "C", "<locals of function 'm'>"]`.
|
|
||||||
fn qualified_name_components(self, db: &'db dyn Db) -> Vec<String> {
|
|
||||||
let body_scope = self.body_scope(db);
|
|
||||||
let file = body_scope.file(db);
|
|
||||||
let module_ast = parsed_module(db, file).load(db);
|
|
||||||
let index = semantic_index(db, file);
|
|
||||||
let file_scope_id = body_scope.file_scope_id(db);
|
|
||||||
|
|
||||||
let mut name_parts = vec![];
|
|
||||||
|
|
||||||
// Skips itself
|
|
||||||
for (_, ancestor_scope) in index.ancestor_scopes(file_scope_id).skip(1) {
|
|
||||||
let node = ancestor_scope.node();
|
|
||||||
|
|
||||||
match ancestor_scope.kind() {
|
|
||||||
ScopeKind::Class => {
|
|
||||||
if let Some(class_def) = node.as_class() {
|
|
||||||
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScopeKind::Function => {
|
|
||||||
if let Some(function_def) = node.as_function() {
|
|
||||||
name_parts.push(format!(
|
|
||||||
"<locals of function '{}'>",
|
|
||||||
function_def.node(&module_ast).name.as_str()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(module) = file_to_module(db, file) {
|
|
||||||
let module_name = module.name(db);
|
|
||||||
name_parts.push(module_name.as_str().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
name_parts.reverse();
|
|
||||||
name_parts
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClassDisplay<'db> {
|
struct ClassDisplay<'db> {
|
||||||
|
|
@ -562,14 +517,14 @@ struct ClassDisplay<'db> {
|
||||||
impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||||
let qualification_level = self.settings.qualified.get(&**self.class.name(self.db));
|
let qualification_level = self.settings.qualified.get(&**self.class.name(self.db));
|
||||||
|
|
||||||
|
let ty = Type::ClassLiteral(self.class);
|
||||||
if qualification_level.is_some() {
|
if qualification_level.is_some() {
|
||||||
for parent in self.class.qualified_name_components(self.db) {
|
write!(f.with_type(ty), "{}", self.class.qualified_name(self.db))?;
|
||||||
f.write_str(&parent)?;
|
} else {
|
||||||
f.write_char('.')?;
|
write!(f.with_type(ty), "{}", self.class.name(self.db))?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
f.with_type(Type::ClassLiteral(self.class))
|
|
||||||
.write_str(self.class.name(self.db))?;
|
|
||||||
if qualification_level == Some(&QualificationLevel::FileAndLineNumber) {
|
if qualification_level == Some(&QualificationLevel::FileAndLineNumber) {
|
||||||
let file = self.class.file(self.db);
|
let file = self.class.file(self.db);
|
||||||
let path = file.path(self.db);
|
let path = file.path(self.db);
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@ impl<'db> AllMembers<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A member of a type with an optional definition.
|
/// A member of a type with an optional definition.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct MemberWithDefinition<'db> {
|
pub struct MemberWithDefinition<'db> {
|
||||||
pub member: Member<'db>,
|
pub member: Member<'db>,
|
||||||
pub definition: Option<Definition<'db>>,
|
pub definition: Option<Definition<'db>>,
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ use crate::types::{
|
||||||
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||||
TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
|
TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
|
||||||
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
|
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
|
||||||
UnionTypeInstance, binding_type, todo_type,
|
UnionTypeInstance, binding_type, liskov, todo_type,
|
||||||
};
|
};
|
||||||
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||||
use crate::unpack::{EvaluationMode, UnpackPosition};
|
use crate::unpack::{EvaluationMode, UnpackPosition};
|
||||||
|
|
@ -548,10 +548,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
"Inferring deferred types should not add more deferred definitions"
|
"Inferring deferred types should not add more deferred definitions"
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Only call this function when diagnostics are enabled.
|
if self.db().should_check_file(self.file()) {
|
||||||
self.check_class_definitions();
|
self.check_class_definitions();
|
||||||
self.check_overloaded_functions(node);
|
self.check_overloaded_functions(node);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all class definitions to check that the definition will not cause an exception
|
/// Iterate over all class definitions to check that the definition will not cause an exception
|
||||||
/// to be raised at runtime. This needs to be done after most other types in the scope have been
|
/// to be raised at runtime. This needs to be done after most other types in the scope have been
|
||||||
|
|
@ -949,6 +950,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (8) Check for Liskov violations
|
||||||
|
liskov::check_class(&self.context, class);
|
||||||
|
|
||||||
if let Some(protocol) = class.into_protocol_class(self.db()) {
|
if let Some(protocol) = class.into_protocol_class(self.db()) {
|
||||||
protocol.validate_members(&self.context);
|
protocol.validate_members(&self.context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
//! Checks relating to the [Liskov Substitution Principle].
|
||||||
|
//!
|
||||||
|
//! [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
|
||||||
|
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
place::Place,
|
||||||
|
semantic_index::place_table,
|
||||||
|
types::{
|
||||||
|
ClassBase, ClassLiteral, ClassType, KnownClass, Type,
|
||||||
|
class::CodeGeneratorKind,
|
||||||
|
context::InferContext,
|
||||||
|
diagnostic::report_invalid_method_override,
|
||||||
|
ide_support::{MemberWithDefinition, all_declarations_and_bindings},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: ClassLiteral<'db>) {
|
||||||
|
let db = context.db();
|
||||||
|
if class.is_known(db, KnownClass::Object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let class_specialized = class.identity_specialization(db);
|
||||||
|
let own_class_members: FxHashSet<_> =
|
||||||
|
all_declarations_and_bindings(db, class.body_scope(db)).collect();
|
||||||
|
|
||||||
|
for member in own_class_members {
|
||||||
|
check_class_declaration(context, class_specialized, &member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_class_declaration<'db>(
|
||||||
|
context: &InferContext<'db, '_>,
|
||||||
|
class: ClassType<'db>,
|
||||||
|
member: &MemberWithDefinition<'db>,
|
||||||
|
) {
|
||||||
|
let db = context.db();
|
||||||
|
|
||||||
|
let MemberWithDefinition { member, definition } = member;
|
||||||
|
|
||||||
|
// TODO: Check Liskov on non-methods too
|
||||||
|
let Type::FunctionLiteral(function) = member.ty else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(definition) = definition else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: classmethods and staticmethods
|
||||||
|
if function.is_classmethod(db) || function.is_staticmethod(db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor methods are not checked for Liskov compliance
|
||||||
|
if matches!(
|
||||||
|
&*member.name,
|
||||||
|
"__init__" | "__new__" | "__post_init__" | "__init_subclass__"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesized `__replace__` methods on dataclasses are not checked
|
||||||
|
if &member.name == "__replace__"
|
||||||
|
&& matches!(
|
||||||
|
CodeGeneratorKind::from_class(db, class.class_literal(db).0, None),
|
||||||
|
Some(CodeGeneratorKind::DataclassLike(_))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Place::Defined(type_on_subclass_instance, _, _) =
|
||||||
|
Type::instance(db, class).member(db, &member.name).place
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for superclass in class.iter_mro(db).skip(1).filter_map(ClassBase::into_class) {
|
||||||
|
let superclass_symbol_table =
|
||||||
|
place_table(db, superclass.class_literal(db).0.body_scope(db));
|
||||||
|
|
||||||
|
let mut method_kind = MethodKind::NotSynthesized;
|
||||||
|
|
||||||
|
// If the member is not defined on the class itself, skip it
|
||||||
|
if let Some(superclass_symbol) = superclass_symbol_table.symbol_by_name(&member.name) {
|
||||||
|
if !(superclass_symbol.is_bound() || superclass_symbol.is_declared()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (superclass_literal, superclass_specialization) = superclass.class_literal(db);
|
||||||
|
if superclass_literal
|
||||||
|
.own_synthesized_member(db, superclass_specialization, None, &member.name)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let class_kind =
|
||||||
|
CodeGeneratorKind::from_class(db, superclass_literal, superclass_specialization);
|
||||||
|
|
||||||
|
method_kind = match class_kind {
|
||||||
|
Some(CodeGeneratorKind::NamedTuple) => {
|
||||||
|
MethodKind::Synthesized(SynthesizedMethodKind::NamedTuple)
|
||||||
|
}
|
||||||
|
Some(CodeGeneratorKind::DataclassLike(_)) => {
|
||||||
|
MethodKind::Synthesized(SynthesizedMethodKind::Dataclass)
|
||||||
|
}
|
||||||
|
// It's invalid to define a method on a `TypedDict` (and this should be
|
||||||
|
// reported elsewhere), but it's valid to override other things on a
|
||||||
|
// `TypedDict`, so this case isn't relevant right now but may become
|
||||||
|
// so when we expand Liskov checking in the future
|
||||||
|
Some(CodeGeneratorKind::TypedDict) => {
|
||||||
|
MethodKind::Synthesized(SynthesizedMethodKind::TypedDict)
|
||||||
|
}
|
||||||
|
None => MethodKind::NotSynthesized,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let Place::Defined(superclass_type, _, _) = Type::instance(db, superclass)
|
||||||
|
.member(db, &member.name)
|
||||||
|
.place
|
||||||
|
else {
|
||||||
|
// If not defined on any superclass, nothing to check
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(superclass_type_as_callable) = superclass_type.try_upcast_to_callable(db) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if type_on_subclass_instance.is_assignable_to(db, superclass_type_as_callable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
report_invalid_method_override(
|
||||||
|
context,
|
||||||
|
&member.name,
|
||||||
|
class,
|
||||||
|
*definition,
|
||||||
|
function,
|
||||||
|
superclass,
|
||||||
|
superclass_type,
|
||||||
|
method_kind,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only one diagnostic should be emitted per each invalid override,
|
||||||
|
// even if it overrides multiple superclasses incorrectly!
|
||||||
|
// It's possible `report_invalid_method_override` didn't emit a diagnostic because there's a
|
||||||
|
// suppression comment, but that too should cause us to exit early here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(super) enum MethodKind {
|
||||||
|
Synthesized(SynthesizedMethodKind),
|
||||||
|
NotSynthesized,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(super) enum SynthesizedMethodKind {
|
||||||
|
NamedTuple,
|
||||||
|
Dataclass,
|
||||||
|
TypedDict,
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,7 @@ Settings: Settings {
|
||||||
"invalid-key": Error (Default),
|
"invalid-key": Error (Default),
|
||||||
"invalid-legacy-type-variable": Error (Default),
|
"invalid-legacy-type-variable": Error (Default),
|
||||||
"invalid-metaclass": Error (Default),
|
"invalid-metaclass": Error (Default),
|
||||||
|
"invalid-method-override": Error (Default),
|
||||||
"invalid-named-tuple": Error (Default),
|
"invalid-named-tuple": Error (Default),
|
||||||
"invalid-newtype": Error (Default),
|
"invalid-newtype": Error (Default),
|
||||||
"invalid-overload": Error (Default),
|
"invalid-overload": Error (Default),
|
||||||
|
|
|
||||||
|
|
@ -613,6 +613,16 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"invalid-method-override": {
|
||||||
|
"title": "detects method definitions that violate the Liskov Substitution Principle",
|
||||||
|
"description": "## What it does\nDetects method overrides that violate the [Liskov Substitution Principle] (\"LSP\").\n\nThe LSP states that an instance of a subtype should be substitutable for an instance of its supertype.\nApplied to Python, this means:\n1. All argument combinations a superclass method accepts\n must also be accepted by an overriding subclass method.\n2. The return type of an overriding subclass method must be a subtype\n of the return type of the superclass method.\n\n## Why is this bad?\nViolating the Liskov Substitution Principle will lead to many of ty's assumptions and\ninferences being incorrect, which will mean that it will fail to catch many possible\ntype errors in your code.\n\n## Example\n```python\nclass Super:\n def method(self, x) -> int:\n return 42\n\nclass Sub(Super):\n # Liskov violation: `str` is not a subtype of `int`,\n # but the supertype method promises to return an `int`.\n def method(self, x) -> str: # error: [invalid-override]\n return \"foo\"\n\ndef accepts_super(s: Super) -> int:\n return s.method(x=42)\n\naccepts_super(Sub()) # The result of this call is a string, but ty will infer\n # it to be an `int` due to the violation of the Liskov Substitution Principle.\n\nclass Sub2(Super):\n # Liskov violation: the superclass method can be called with a `x=`\n # keyword argument, but the subclass method does not accept it.\n def method(self, y) -> int: # error: [invalid-override]\n return 42\n\naccepts_super(Sub2()) # TypeError at runtime: method() got an unexpected keyword argument 'x'\n # ty cannot catch this error due to the violation of the Liskov Substitution Principle.\n```\n\n## Common issues\n\n### Why does ty complain about my `__eq__` method?\n\n`__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary\nobjects as their second argument, for example:\n\n```python\nclass A:\n x: int\n\n def __eq__(self, other: object) -> bool:\n # gracefully handle an object of an unexpected type\n # without raising an exception\n if not isinstance(other, A):\n return False\n return self.x == other.x\n```\n\nIf `A.__eq__` here were annotated as only accepting `A` instances for its second argument,\nit would imply that you wouldn't be able to use `==` between instances of `A` and\ninstances of unrelated classes without an exception possibly being raised. While some\nclasses in Python do indeed behave this way, the strongly held convention is that it should\nbe avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__`\nand `__ne__` methods accept `object` as their second argument.\n\n[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle",
|
||||||
|
"default": "error",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/Level"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"invalid-named-tuple": {
|
"invalid-named-tuple": {
|
||||||
"title": "detects invalid `NamedTuple` class definitions",
|
"title": "detects invalid `NamedTuple` class definitions",
|
||||||
"description": "## What it does\nChecks for invalidly defined `NamedTuple` classes.\n\n## Why is this bad?\nAn invalidly defined `NamedTuple` class may lead to the type checker\ndrawing incorrect conclusions. It may also lead to `TypeError`s at runtime.\n\n## Examples\nA class definition cannot combine `NamedTuple` with other base classes\nin multiple inheritance; doing so raises a `TypeError` at runtime. The sole\nexception to this rule is `Generic[]`, which can be used alongside `NamedTuple`\nin a class's bases list.\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple, object): ...\nTypeError: can only inherit from a NamedTuple type and Generic\n```",
|
"description": "## What it does\nChecks for invalidly defined `NamedTuple` classes.\n\n## Why is this bad?\nAn invalidly defined `NamedTuple` class may lead to the type checker\ndrawing incorrect conclusions. It may also lead to `TypeError`s at runtime.\n\n## Examples\nA class definition cannot combine `NamedTuple` with other base classes\nin multiple inheritance; doing so raises a `TypeError` at runtime. The sole\nexception to this rule is `Generic[]`, which can be used alongside `NamedTuple`\nin a class's bases list.\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple, object): ...\nTypeError: can only inherit from a NamedTuple type and Generic\n```",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue