Compare commits
18 Commits
alex/subsc
...
dcreager/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0e20ce8da | ||
|
|
cd7fae4968 | ||
|
|
7e5f1257d7 | ||
|
|
8c8173c626 | ||
|
|
ecbd3d7539 | ||
|
|
ede31a9dc3 | ||
|
|
69ae228db7 | ||
|
|
d8a8d343c9 | ||
|
|
83e749bfe2 | ||
|
|
02fed53477 | ||
|
|
d3853442da | ||
|
|
b6e790a84b | ||
|
|
a960a4a5df | ||
|
|
7f0ce3e88d | ||
|
|
ba0736385d | ||
|
|
e41f045ec5 | ||
|
|
b24afb643c | ||
|
|
853bb00626 |
194
crates/ty/docs/rules.md
generated
194
crates/ty/docs/rules.md
generated
@@ -8,7 +8,7 @@
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.7">0.0.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L160" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ def f(x: object):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L211" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ f(int) # error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L237" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ a = 1
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L262" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L288" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ class B(A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L314" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ type B = A
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L357" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L358" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -346,7 +346,7 @@ false positives it can produce.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L378" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L379" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L400" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -579,13 +579,47 @@ t = (0, 1, 2)
|
||||
t[3] # IndexError: tuple index out of range
|
||||
```
|
||||
|
||||
## `ineffective-final`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.33">0.0.1-alpha.33</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ineffective-final" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1793" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for calls to `final()` that type checkers cannot interpret.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
The `final()` function is designed to be used as a decorator. When called directly
|
||||
as a function (e.g., `final(type(...))`), type checkers will not understand the
|
||||
application of `final` and will not prevent subclassing.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
from typing import final
|
||||
|
||||
# Incorrect: type checkers will not prevent subclassing
|
||||
MyClass = final(type("MyClass", (), {}))
|
||||
|
||||
# Correct: use `final` as a decorator
|
||||
@final
|
||||
class MyClass: ...
|
||||
```
|
||||
|
||||
## `instance-layout-conflict`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L432" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -674,7 +708,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -701,7 +735,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L743" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L744" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -729,7 +763,7 @@ a: int = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2122" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2151" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -763,7 +797,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -799,7 +833,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L795" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L796" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -823,7 +857,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L880" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L881" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -850,7 +884,7 @@ with 1:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L902" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -879,7 +913,7 @@ a: str
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -923,7 +957,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1792" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1821" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -965,7 +999,7 @@ class D(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2373" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2402" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1009,7 +1043,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1003" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1047,7 +1081,7 @@ class D(Generic[U, T]): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.12">0.0.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-enum" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960" 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>
|
||||
|
||||
|
||||
@@ -1126,7 +1160,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L670" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L671" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1165,7 +1199,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1033" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1200,7 +1234,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1130" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1131" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1234,7 +1268,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2275" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2304" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1341,7 +1375,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L577" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L578" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1395,7 +1429,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106" 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>
|
||||
|
||||
|
||||
@@ -1425,7 +1459,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1158" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1475,7 +1509,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1257" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1501,7 +1535,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1532,7 +1566,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L514" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1600,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1277" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1615,7 +1649,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L724" 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>
|
||||
|
||||
|
||||
@@ -1640,7 +1674,7 @@ def func() -> int:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1319" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1320" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1736,7 +1770,7 @@ class C: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.10">0.0.10</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-total-ordering" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2411" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2440" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1782,7 +1816,7 @@ class MyClass:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1809,7 +1843,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1551" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1552" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1856,7 +1890,7 @@ Bar[int] # error: too few arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1358" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1886,7 +1920,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1383" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1916,7 +1950,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1950,7 +1984,7 @@ f(10) # Error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1406" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1407" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1984,7 +2018,7 @@ class C:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1462" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2019,7 +2053,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.9">0.0.9</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-typed-dict-statement" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2250" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2279" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2050,7 +2084,7 @@ class Foo(TypedDict):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1492" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2075,7 +2109,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2223" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2252" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2108,7 +2142,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1510" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1511" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2137,7 +2171,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1592" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1593" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2163,7 +2197,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1533" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1534" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2187,7 +2221,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2220,7 +2254,7 @@ class B(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2247,7 +2281,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2005" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2274,7 +2308,7 @@ f(x=1) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1665" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2302,7 +2336,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L184" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2334,7 +2368,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1686" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1687" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2371,7 +2405,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1717" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2435,7 +2469,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2150" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2179" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2462,7 +2496,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2098" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2127" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2492,7 +2526,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1742" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1743" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2521,7 +2555,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1910" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1939" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2555,7 +2589,7 @@ class F(NamedTuple):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1850" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1879" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2582,7 +2616,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1828" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1857" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2610,7 +2644,7 @@ def _(x: int):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1871" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1900" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2656,7 +2690,7 @@ class A:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1937" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1966" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2680,7 +2714,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1955" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1984" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2707,7 +2741,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2026" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2735,7 +2769,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2171" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2200" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2793,7 +2827,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2019" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2048" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2818,7 +2852,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2038" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2067" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2843,7 +2877,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L814" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2882,7 +2916,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1612" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1613" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2919,7 +2953,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-dynamic-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2960,7 +2994,7 @@ def factory(base: type[Base]) -> type:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2057" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2086" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -3024,7 +3058,7 @@ to `false` to prevent this rule from reporting unused `type: ignore` comments.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1201" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -3087,7 +3121,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2079" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2108" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -2585,7 +2585,7 @@ type<CURSOR>
|
||||
|
||||
assert_snapshot!(
|
||||
test.type_signatures().skip_auto_import().build().snapshot(),
|
||||
@r"
|
||||
@"
|
||||
type :: <class 'type'>
|
||||
TypeError :: <class 'TypeError'>
|
||||
",
|
||||
@@ -8009,7 +8009,7 @@ my_list[0].remove<CURSOR>
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@r"
|
||||
@"
|
||||
removeprefix
|
||||
removesuffix
|
||||
",
|
||||
@@ -8030,7 +8030,7 @@ def f(x: Any | str):
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@r"
|
||||
@"
|
||||
removeprefix
|
||||
removesuffix
|
||||
",
|
||||
@@ -8052,7 +8052,7 @@ def f(x: Intersection[int, Any] | str):
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@r"
|
||||
@"
|
||||
removeprefix
|
||||
removesuffix
|
||||
",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
use regex::Regex;
|
||||
use ruff_python_trivia::{PythonWhitespace, leading_indentation};
|
||||
use ruff_source_file::UniversalNewlines;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
@@ -349,16 +350,37 @@ fn render_markdown(docstring: &str) -> String {
|
||||
| "versionchanged" | "version-changed" | "version-deprecated" | "deprecated"
|
||||
| "version-removed" | "versionremoved",
|
||||
) => {
|
||||
// Map version directives to human-readable phrases (matching Sphinx output)
|
||||
let pretty_directive = match directive.unwrap() {
|
||||
"versionadded" | "version-added" => Cow::Borrowed("Added in version"),
|
||||
"versionchanged" | "version-changed" => Cow::Borrowed("Changed in version"),
|
||||
"deprecated" | "version-deprecated" => {
|
||||
Cow::Borrowed("Deprecated since version")
|
||||
}
|
||||
"versionremoved" | "version-removed" => Cow::Borrowed("Removed in version"),
|
||||
other => Cow::Owned(
|
||||
other
|
||||
.char_indices()
|
||||
.map(|(index, c)| {
|
||||
if index == 0 {
|
||||
c.to_ascii_uppercase()
|
||||
} else {
|
||||
c
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
};
|
||||
|
||||
// Render the argument of things like `.. version-added:: 4.0`
|
||||
let suffix = if let Some(lang) = lang {
|
||||
format!(" *{lang}*")
|
||||
format!(" {lang}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
// We prepend without_directive here out of caution for preserving input.
|
||||
// This is probably gibberish/invalid syntax? But it's a no-op in normal cases.
|
||||
temp_owned_line =
|
||||
format!("**{without_directive}{}:**{suffix}", directive.unwrap());
|
||||
temp_owned_line = format!("**{without_directive}{pretty_directive}{suffix}:**");
|
||||
|
||||
line = temp_owned_line.as_str();
|
||||
None
|
||||
@@ -1076,7 +1098,7 @@ mod tests {
|
||||
assert_snapshot!(docstring.render_markdown(), @r#"
|
||||
The thing you need to understand is that computers are hard.
|
||||
|
||||
**warning:**
|
||||
**Warning:**
|
||||
Now listen here buckaroo you might have seen me say computers are hard,
|
||||
and though "yeah I know computers are hard but NO you DON'T KNOW.
|
||||
|
||||
@@ -1115,12 +1137,12 @@ mod tests {
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
Some much-updated docs
|
||||
|
||||
**version-added:** *3.0*
|
||||
**Added in version 3.0:**
|
||||
Function added
|
||||
|
||||
**version-changed:** *4.0*
|
||||
**Changed in version 4.0:**
|
||||
The `spam` argument was added
|
||||
**version-changed:** *4.1*
|
||||
**Changed in version 4.1:**
|
||||
The `spam` argument is considered evil now.
|
||||
|
||||
You really shouldnt use it
|
||||
@@ -1141,7 +1163,7 @@ mod tests {
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
**wow this is some changes deprecated:** *1.2.3*
|
||||
**wow this is some changes Deprecated since version 1.2.3:**
|
||||
x = 2
|
||||
");
|
||||
}
|
||||
@@ -1151,7 +1173,7 @@ mod tests {
|
||||
fn explicit_markdown_block_with_ps1_contents() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
```python
|
||||
>>> thing.do_thing()
|
||||
wow it did the thing
|
||||
@@ -1162,7 +1184,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
```python
|
||||
@@ -1179,7 +1201,7 @@ mod tests {
|
||||
fn explicit_markdown_block_with_underscore_contents_tick() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
`````python
|
||||
x_y = thing_do();
|
||||
``` # this should't close the fence!
|
||||
@@ -1189,7 +1211,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
`````python
|
||||
@@ -1205,7 +1227,7 @@ mod tests {
|
||||
fn explicit_markdown_block_with_underscore_contents_tilde() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
~~~~~python
|
||||
x_y = thing_do();
|
||||
~~~ # this should't close the fence!
|
||||
@@ -1215,7 +1237,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
~~~~~python
|
||||
@@ -1233,7 +1255,7 @@ mod tests {
|
||||
fn explicit_markdown_block_with_indent_tick() {
|
||||
let docstring = r#"
|
||||
My cool func...
|
||||
|
||||
|
||||
Returns:
|
||||
Some details
|
||||
`````python
|
||||
@@ -1246,7 +1268,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func...
|
||||
|
||||
Returns:
|
||||
@@ -1267,7 +1289,7 @@ mod tests {
|
||||
fn explicit_markdown_block_with_indent_tilde() {
|
||||
let docstring = r#"
|
||||
My cool func...
|
||||
|
||||
|
||||
Returns:
|
||||
Some details
|
||||
~~~~~~python
|
||||
@@ -1280,7 +1302,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func...
|
||||
|
||||
Returns:
|
||||
@@ -1299,14 +1321,14 @@ mod tests {
|
||||
fn explicit_markdown_block_with_unclosed_fence_tick() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
````python
|
||||
x_y = thing_do();
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
````python
|
||||
@@ -1320,14 +1342,14 @@ mod tests {
|
||||
fn explicit_markdown_block_with_unclosed_fence_tilde() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
~~~~~python
|
||||
x_y = thing_do();
|
||||
"#;
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
~~~~~python
|
||||
@@ -1342,7 +1364,7 @@ mod tests {
|
||||
fn explicit_markdown_block_messy_corners_tick() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
``````we still think this is a codefence```
|
||||
x_y = thing_do();
|
||||
```````````` and are sloppy as heck with indentation and closing shrugggg
|
||||
@@ -1350,7 +1372,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
``````we still think this is a codefence```
|
||||
@@ -1365,7 +1387,7 @@ mod tests {
|
||||
fn explicit_markdown_block_messy_corners_tilde() {
|
||||
let docstring = r#"
|
||||
My cool func:
|
||||
|
||||
|
||||
~~~~~~we still think this is a codefence~~~
|
||||
x_y = thing_do();
|
||||
~~~~~~~~~~~~~ and are sloppy as heck with indentation and closing shrugggg
|
||||
@@ -1373,7 +1395,7 @@ mod tests {
|
||||
|
||||
let docstring = Docstring::new(docstring.to_owned());
|
||||
|
||||
assert_snapshot!(docstring.render_markdown(), @r"
|
||||
assert_snapshot!(docstring.render_markdown(), @"
|
||||
My cool func:
|
||||
|
||||
~~~~~~we still think this is a codefence~~~
|
||||
|
||||
@@ -1649,6 +1649,65 @@ Traceb<CURSOR>ackType
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
/// goto-definition on a dynamic class literal (created via `type()`)
|
||||
#[test]
|
||||
fn goto_definition_dynamic_class_literal() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
DynClass = type("DynClass", (), {})
|
||||
|
||||
x = DynCla<CURSOR>ss()
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r#"
|
||||
info[goto-definition]: Go to definition
|
||||
--> main.py:4:5
|
||||
|
|
||||
2 | DynClass = type("DynClass", (), {})
|
||||
3 |
|
||||
4 | x = DynClass()
|
||||
| ^^^^^^^^ Clicking here
|
||||
|
|
||||
info: Found 2 definitions
|
||||
--> main.py:2:1
|
||||
|
|
||||
2 | DynClass = type("DynClass", (), {})
|
||||
| --------
|
||||
3 |
|
||||
4 | x = DynClass()
|
||||
|
|
||||
::: stdlib/builtins.pyi:137:9
|
||||
|
|
||||
135 | def __class__(self, type: type[Self], /) -> None: ...
|
||||
136 | def __init__(self) -> None: ...
|
||||
137 | def __new__(cls) -> Self: ...
|
||||
| -------
|
||||
138 | # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
|
||||
139 | # Overriding them in subclasses has different semantics, even if the override has an identical signature.
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
/// goto-definition on a dangling dynamic class literal (not assigned to a variable)
|
||||
#[test]
|
||||
fn goto_definition_dangling_dynamic_class_literal() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
class Foo(type("Ba<CURSOR>r", (), {})):
|
||||
pass
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
// TODO: Should only list `a: int`
|
||||
#[test]
|
||||
fn redeclarations() {
|
||||
|
||||
@@ -20,16 +20,13 @@ class Base: ...
|
||||
class Mixin: ...
|
||||
|
||||
# We synthesize a class type using the name argument
|
||||
Foo = type("Foo", (), {})
|
||||
reveal_type(Foo) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (), {})) # revealed: <class 'Foo'>
|
||||
|
||||
# With a single base class
|
||||
Foo2 = type("Foo", (Base,), {"attr": 1})
|
||||
reveal_type(Foo2) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: <class 'Foo'>
|
||||
|
||||
# With multiple base classes
|
||||
Foo3 = type("Foo", (Base, Mixin), {})
|
||||
reveal_type(Foo3) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Foo", (Base, Mixin), {})) # revealed: <class 'Foo'>
|
||||
|
||||
# The inferred type is assignable to type[Base] since Foo inherits from Base
|
||||
tests: list[type[Base]] = []
|
||||
@@ -499,39 +496,38 @@ Other numbers of arguments are invalid:
|
||||
|
||||
```py
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Foo", ())) # revealed: Unknown
|
||||
reveal_type(type("Foo", ())) # revealed: type[Unknown]
|
||||
|
||||
# TODO: the keyword arguments for `Foo`/`Bar`/`Baz` here are invalid
|
||||
# (you cannot pass `metaclass=` to `type()`, and none of them have
|
||||
# base classes with `__init_subclass__` methods),
|
||||
# but `type[Unknown]` would be better than `Unknown` here
|
||||
# (none of them have base classes with `__init_subclass__` methods).
|
||||
#
|
||||
# The intent to create a new class is however clear,
|
||||
# so we still infer these as class-literal types.
|
||||
reveal_type(type("Foo", (), {}, weird_other_arg=42)) # revealed: <class 'Foo'>
|
||||
reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: <class 'Bar'>
|
||||
|
||||
# You can't pass `metaclass=` to the `type()` constructor, but the intent is clear,
|
||||
# so we infer `<class 'Baz'>` here rather than `type[Unknown]`
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Foo", (), {}, weird_other_arg=42)) # revealed: Unknown
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: Unknown
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: Unknown
|
||||
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: <class 'Baz'>
|
||||
```
|
||||
|
||||
The following calls are also invalid, due to incorrect argument types.
|
||||
|
||||
Inline calls (not assigned to a variable) fall back to regular `type` overload matching, which
|
||||
produces slightly different error messages than assigned dynamic class creation:
|
||||
The following calls are also invalid, due to incorrect argument types:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `Literal[b"Foo"]`"
|
||||
type(b"Foo", (), {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 2 (`bases`) of `type()`: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
type("Foo", Base, {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
# error: 14 [invalid-base] "Invalid class base with type `Literal[1]`"
|
||||
# error: 17 [invalid-base] "Invalid class base with type `Literal[2]`"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# error: 22 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 3 (`namespace`) of `type()`: Expected `dict[str, Any]`, found `dict[Unknown | bytes, Unknown | int]`"
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
@@ -598,6 +594,19 @@ class Y(C, B): ...
|
||||
Conflict = type("Conflict", (X, Y), {})
|
||||
```
|
||||
|
||||
## MRO error highlighting (snapshot)
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
This snapshot test documents the diagnostic highlighting range for dynamic class literals.
|
||||
Currently, the entire `type()` call expression is highlighted:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
|
||||
```
|
||||
|
||||
## Metaclass conflicts
|
||||
|
||||
Metaclass conflicts are detected and reported:
|
||||
@@ -853,55 +862,77 @@ def f(*args, **kwargs):
|
||||
|
||||
# Has a string first arg, but unknown additional args from *args
|
||||
B = type("B", *args, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(B) # revealed: <class 'str'>
|
||||
reveal_type(B) # revealed: type[Unknown]
|
||||
|
||||
# Has string and tuple, but unknown additional args
|
||||
C = type("C", (), *args, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(C) # revealed: type
|
||||
reveal_type(C) # revealed: type[Unknown]
|
||||
|
||||
# All three positional args provided, only **kwargs unknown
|
||||
D = type("D", (), {}, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(D) # revealed: type
|
||||
reveal_type(D) # revealed: <class 'D'>
|
||||
|
||||
# Three starred expressions - we can't know how they expand
|
||||
a = ("E",)
|
||||
b = ((),)
|
||||
c = ({},)
|
||||
E = type(*a, *b, *c)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(E) # revealed: type
|
||||
reveal_type(E) # revealed: type[Unknown]
|
||||
```
|
||||
|
||||
## Explicit type annotations
|
||||
|
||||
TODO: Annotated assignments with `type()` calls don't currently synthesize the specific class type.
|
||||
This will be fixed when we support all `type()` calls (including inline) via generic handling.
|
||||
When an explicit type annotation is provided, the inferred type is checked against it:
|
||||
|
||||
```py
|
||||
# The annotation `type` is compatible with the inferred class literal type
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: <class 'T'>
|
||||
|
||||
# The annotation `type[Base]` is compatible with the inferred type
|
||||
class Base: ...
|
||||
|
||||
# TODO: Should infer `<class 'T'>` instead of `type`
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: type
|
||||
|
||||
# TODO: Should infer `<class 'Derived'>` instead of `type[Base]}
|
||||
# error: [invalid-assignment] "Object of type `type` is not assignable to `type[Base]`"
|
||||
Derived: type[Base] = type("Derived", (Base,), {})
|
||||
reveal_type(Derived) # revealed: type[Base]
|
||||
reveal_type(Derived) # revealed: <class 'Derived'>
|
||||
|
||||
# Incompatible annotation produces an error
|
||||
class Unrelated: ...
|
||||
|
||||
# error: [invalid-assignment]
|
||||
Bad: type[Unrelated] = type("Bad", (Base,), {})
|
||||
```
|
||||
|
||||
## Special base classes
|
||||
|
||||
Some special base classes work with dynamic class creation, but special semantics may not be fully
|
||||
synthesized:
|
||||
synthesized.
|
||||
|
||||
### Invalid special bases
|
||||
|
||||
Dynamic classes cannot directly inherit from `Generic`, `Protocol`, or `TypedDict`. These special
|
||||
forms require class syntax for their semantics to be properly applied:
|
||||
|
||||
```py
|
||||
from typing import Generic, Protocol, TypeVar
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-base] "Invalid base for class created via `type()`"
|
||||
GenericClass = type("GenericClass", (Generic[T],), {})
|
||||
|
||||
# error: [unsupported-dynamic-base] "Unsupported base for class created via `type()`"
|
||||
ProtocolClass = type("ProtocolClass", (Protocol,), {})
|
||||
|
||||
# error: [invalid-base] "Invalid base for class created via `type()`"
|
||||
TypedDictClass = type("TypedDictClass", (TypedDict,), {})
|
||||
```
|
||||
|
||||
### Protocol bases
|
||||
|
||||
Inheriting from a class that is itself a protocol is valid:
|
||||
|
||||
```py
|
||||
# Protocol bases work - the class is created as a subclass of the protocol
|
||||
from typing import Protocol
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
@@ -918,8 +949,9 @@ reveal_type(instance) # revealed: ProtoImpl
|
||||
|
||||
### TypedDict bases
|
||||
|
||||
Inheriting from a class that is itself a TypedDict is valid:
|
||||
|
||||
```py
|
||||
# TypedDict bases work but TypedDict semantics aren't applied to dynamic subclasses
|
||||
from typing_extensions import TypedDict
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
@@ -952,26 +984,26 @@ reveal_mro(Point3D) # revealed: (<class 'Point3D'>, <class 'Point'>, <class 'tu
|
||||
|
||||
### Enum bases
|
||||
|
||||
Creating a class via `type()` that inherits from any Enum class fails at runtime because `EnumMeta`
|
||||
expects special attributes in the class dict that `type()` doesn't provide:
|
||||
|
||||
```py
|
||||
# Enum subclassing via type() is not supported - EnumMeta requires special dict handling
|
||||
# that type() cannot provide. This applies even to empty enums.
|
||||
from enum import Enum
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
|
||||
# Enums with members are final and cannot be subclassed
|
||||
# error: [subclass-of-final-class]
|
||||
ExtendedColor = type("ExtendedColor", (Color,), {})
|
||||
|
||||
class EmptyEnum(Enum):
|
||||
pass
|
||||
|
||||
# TODO: We should emit a diagnostic here - type() cannot create Enum subclasses
|
||||
ExtendedColor = type("ExtendedColor", (Color,), {})
|
||||
reveal_type(ExtendedColor) # revealed: <class 'ExtendedColor'>
|
||||
|
||||
# Even empty enums fail - EnumMeta requires special dict handling
|
||||
# TODO: We should emit a diagnostic here too
|
||||
ValidExtension = type("ValidExtension", (EmptyEnum,), {})
|
||||
reveal_type(ValidExtension) # revealed: <class 'ValidExtension'>
|
||||
# Empty enums fail because EnumMeta requires special dict handling
|
||||
# error: [invalid-base] "Invalid base for class created via `type()`"
|
||||
InvalidExtension = type("InvalidExtension", (EmptyEnum,), {})
|
||||
```
|
||||
|
||||
## `__init_subclass__` keyword arguments
|
||||
@@ -992,9 +1024,6 @@ class Child(Base, required_arg="value"):
|
||||
# The dynamically assigned attribute has Unknown in its type
|
||||
reveal_type(Child.config) # revealed: Unknown | str
|
||||
|
||||
# Dynamic class creation - keyword arguments are not yet supported
|
||||
# TODO: This should work: type("DynamicChild", (Base,), {}, required_arg="value")
|
||||
# error: [no-matching-overload]
|
||||
DynamicChild = type("DynamicChild", (Base,), {}, required_arg="value")
|
||||
```
|
||||
|
||||
@@ -1034,3 +1063,27 @@ reveal_type(Dynamic) # revealed: <class 'Dynamic'>
|
||||
# Metaclass attributes are accessible on the class
|
||||
reveal_type(Dynamic.custom_attr) # revealed: str
|
||||
```
|
||||
|
||||
## `final()` on dynamic classes
|
||||
|
||||
Using `final()` as a function (not a decorator) on dynamic classes will not be understood by type
|
||||
checkers. The class is passed through unchanged, and subclassing will not be prevented:
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
# error: [ineffective-final]
|
||||
FinalClass = final(type("FinalClass", (), {}))
|
||||
reveal_type(FinalClass) # revealed: <class 'FinalClass'>
|
||||
|
||||
# Subclassing is allowed because type checkers don't understand `final()` called as a function
|
||||
class Child(FinalClass): ...
|
||||
|
||||
# Same with base classes
|
||||
class Base: ...
|
||||
|
||||
# error: [ineffective-final]
|
||||
FinalDerived = final(type("FinalDerived", (Base,), {}))
|
||||
|
||||
class Child2(FinalDerived): ...
|
||||
```
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Unsupported base for dynamic `type()` classes
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## `@final` class
|
||||
|
||||
Classes decorated with `@final` cannot be subclassed:
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class FinalClass:
|
||||
pass
|
||||
|
||||
X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
|
||||
```
|
||||
|
||||
## `Generic` base
|
||||
|
||||
Dynamic classes created via `type()` cannot inherit from `Generic`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
X = type("X", (Generic[T],), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
## `Protocol` base
|
||||
|
||||
Dynamic classes created via `type()` cannot inherit from `Protocol`:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
|
||||
```
|
||||
|
||||
## `TypedDict` base
|
||||
|
||||
Dynamic classes created via `type()` cannot inherit from `TypedDict` directly. Use
|
||||
`TypedDict("Name", ...)` instead:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
X = type("X", (TypedDict,), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
## Enum base
|
||||
|
||||
Dynamic classes created via `type()` cannot inherit from Enum classes because `EnumMeta` expects
|
||||
special dict attributes that `type()` doesn't provide:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class MyEnum(Enum):
|
||||
pass
|
||||
|
||||
X = type("X", (MyEnum,), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
## Enum with members
|
||||
|
||||
Enums with members are final and cannot be subclassed at all:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
|
||||
X = type("X", (Color,), {}) # error: [subclass-of-final-class]
|
||||
```
|
||||
@@ -210,12 +210,15 @@ Narrowing does not occur in the same way if `type` is used to dynamically create
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
# Inline type() calls fall back to regular type overload matching.
|
||||
# TODO: Once inline type() calls synthesize class types, this should narrow x to Never.
|
||||
# The following diagnostic is valid, since the three-argument form of `type`
|
||||
# can only be called with `str` as the first argument.
|
||||
#
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
|
||||
# error: [invalid-argument-type] "Invalid argument to parameter 1 (`name`) of `type()`: Expected `str`, found `str | int`"
|
||||
if type(x, (), {}) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
# But we synthesize a new class object as the result of a three-argument call to `type`,
|
||||
# and we know that this synthesized class object is not the same object as the `str` class object,
|
||||
# so here the type is narrowed to `Never`!
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
@@ -25,6 +25,7 @@ error[no-matching-overload]: No overload of class `type` matches arguments
|
||||
1 | type() # error: [no-matching-overload]
|
||||
| ^^^^^^
|
||||
|
|
||||
help: `builtins.type()` can either be called with one or three positional arguments (got 0)
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: type.md - Calls to `type()` - MRO error highlighting (snapshot)
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/call/type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class A: ...
|
||||
2 |
|
||||
3 | Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class <class 'A'> in class `Dup`
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | class A: ...
|
||||
2 |
|
||||
3 | Dup = type("Dup", (A, A), {}) # error: [duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - Enum base
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from enum import Enum
|
||||
2 |
|
||||
3 | class MyEnum(Enum):
|
||||
4 | pass
|
||||
5 |
|
||||
6 | X = type("X", (MyEnum,), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid base for class created via `type()`
|
||||
--> src/mdtest_snippet.py:6:16
|
||||
|
|
||||
4 | pass
|
||||
5 |
|
||||
6 | X = type("X", (MyEnum,), {}) # error: [invalid-base]
|
||||
| ^^^^^^ Has type `<class 'MyEnum'>`
|
||||
|
|
||||
info: Creating an enum class via `type()` is not supported
|
||||
info: Consider using `Enum("X", [])` instead
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - Enum with members
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from enum import Enum
|
||||
2 |
|
||||
3 | class Color(Enum):
|
||||
4 | RED = 1
|
||||
5 | GREEN = 2
|
||||
6 |
|
||||
7 | X = type("X", (Color,), {}) # error: [subclass-of-final-class]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[subclass-of-final-class]: Class `X` cannot inherit from final class `Color`
|
||||
--> src/mdtest_snippet.py:7:16
|
||||
|
|
||||
5 | GREEN = 2
|
||||
6 |
|
||||
7 | X = type("X", (Color,), {}) # error: [subclass-of-final-class]
|
||||
| ^^^^^
|
||||
|
|
||||
info: rule `subclass-of-final-class` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `@final` class
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import final
|
||||
2 |
|
||||
3 | @final
|
||||
4 | class FinalClass:
|
||||
5 | pass
|
||||
6 |
|
||||
7 | X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[subclass-of-final-class]: Class `X` cannot inherit from final class `FinalClass`
|
||||
--> src/mdtest_snippet.py:7:16
|
||||
|
|
||||
5 | pass
|
||||
6 |
|
||||
7 | X = type("X", (FinalClass,), {}) # error: [subclass-of-final-class]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
info: rule `subclass-of-final-class` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `Generic` base
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import Generic, TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T")
|
||||
4 |
|
||||
5 | X = type("X", (Generic[T],), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid base for class created via `type()`
|
||||
--> src/mdtest_snippet.py:5:16
|
||||
|
|
||||
3 | T = TypeVar("T")
|
||||
4 |
|
||||
5 | X = type("X", (Generic[T],), {}) # error: [invalid-base]
|
||||
| ^^^^^^^^^^ Has type `<special-form 'typing.Generic[T]'>`
|
||||
|
|
||||
info: Classes created via `type()` cannot be generic
|
||||
info: Consider using `class X(Generic[...]): ...` instead
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `Protocol` base
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import Protocol
|
||||
2 |
|
||||
3 | X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[unsupported-dynamic-base]: Unsupported base for class created via `type()`
|
||||
--> src/mdtest_snippet.py:3:16
|
||||
|
|
||||
1 | from typing import Protocol
|
||||
2 |
|
||||
3 | X = type("X", (Protocol,), {}) # error: [unsupported-dynamic-base]
|
||||
| ^^^^^^^^ Has type `<special-form 'typing.Protocol'>`
|
||||
|
|
||||
info: Classes created via `type()` cannot be protocols
|
||||
info: Consider using `class X(Protocol): ...` instead
|
||||
info: rule `unsupported-dynamic-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: unsupported_base_dynamic_type.md - Unsupported base for dynamic `type()` classes - `TypedDict` base
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/unsupported_base_dynamic_type.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import TypedDict
|
||||
2 |
|
||||
3 | X = type("X", (TypedDict,), {}) # error: [invalid-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid base for class created via `type()`
|
||||
--> src/mdtest_snippet.py:3:16
|
||||
|
|
||||
1 | from typing_extensions import TypedDict
|
||||
2 |
|
||||
3 | X = type("X", (TypedDict,), {}) # error: [invalid-base]
|
||||
| ^^^^^^^^^ Has type `<special-form 'typing.TypedDict'>`
|
||||
|
|
||||
info: Classes created via `type()` cannot be TypedDicts
|
||||
info: Consider using `TypedDict("X", {})` instead
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
# Subscripts involving type aliases
|
||||
|
||||
Aliases are expanded during analysis of subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
def f(
|
||||
implicit_tuple: ImplicitTuple,
|
||||
pep_613_tuple: PEP613Tuple,
|
||||
pep_695_tuple: PEP695Tuple,
|
||||
implicit_zero: ImplicitZero,
|
||||
pep_613_zero: PEP613Zero,
|
||||
pep_695_zero: PEP695Zero,
|
||||
):
|
||||
reveal_type(implicit_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(implicit_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_613_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_613_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_695_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_695_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_695_zero]) # revealed: str
|
||||
```
|
||||
@@ -106,5 +106,5 @@ class Bar:
|
||||
def f(x: Foo):
|
||||
if isinstance(x, Bar):
|
||||
# TODO: should be `int`
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
```
|
||||
|
||||
@@ -80,17 +80,6 @@ def _(m: int, n: int, s2: str):
|
||||
reveal_type(substring2) # revealed: str
|
||||
```
|
||||
|
||||
## LiteralString
|
||||
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
def f(x: LiteralString):
|
||||
reveal_type(x[0]) # revealed: LiteralString
|
||||
reveal_type(x[True]) # revealed: LiteralString
|
||||
reveal_type(x[1:42]) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
```py
|
||||
|
||||
@@ -430,5 +430,5 @@ class Bar: ...
|
||||
|
||||
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
|
||||
# TODO: should be `Foo & Bar`
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
```
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# Subscripts involving type variables
|
||||
|
||||
## TypeVar bound/constrained to a tuple/int-literal/bool-literal
|
||||
|
||||
The upper bounds of type variables are considered when analysing subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
# fmt: off
|
||||
|
||||
def f[
|
||||
BoundedTupleT: tuple[str, int, bytes],
|
||||
ConstrainedTupleT: (tuple[str, int, bytes], tuple[int, bytes, str]),
|
||||
BoundedZeroT: Literal[0],
|
||||
ConstrainedIntLiteralT: (Literal[0], Literal[1])
|
||||
](
|
||||
tuple_1: BoundedTupleT,
|
||||
tuple_2: ConstrainedTupleT,
|
||||
zero: BoundedZeroT,
|
||||
some_integer: ConstrainedIntLiteralT,
|
||||
):
|
||||
# TODO: would ideally be `tuple[str, int]`
|
||||
reveal_type(tuple_1[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_1[zero]) # revealed: str
|
||||
|
||||
# TODO: ideally this might be `str | int`,
|
||||
# but it's hard to do that without introducing false positives elsewhere
|
||||
reveal_type(tuple_1[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# TODO: would ideally be `tuple[str, int] | tuple[int, bytes]`
|
||||
reveal_type(tuple_2[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_2[zero]) # revealed: str | int
|
||||
reveal_type(tuple_2[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
## TypeVars
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class SupportsLessThan(Protocol):
|
||||
def __lt__(self, other, /) -> bool: ...
|
||||
|
||||
def f[K: SupportsLessThan](dictionary: dict[K, int], key: K):
|
||||
reveal_type(dictionary[key]) # revealed: int
|
||||
```
|
||||
|
||||
## ParamSpecs
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def decorator[**P, T](func: Callable[P, T]) -> Callable[P, T]:
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
if len(args) > 0:
|
||||
# error: [invalid-assignment]
|
||||
args = args[1:]
|
||||
|
||||
# `func` requires the full `ParamSpec` passed into `decorator`,
|
||||
# but here the first argument is skipped, so we should possibly emit an error here:
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
```
|
||||
@@ -938,6 +938,18 @@ impl DefinitionKind<'_> {
|
||||
| DefinitionKind::ExceptHandler(_) => DefinitionCategory::Binding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value expression for assignment-based definitions.
|
||||
///
|
||||
/// Returns `Some` for `Assignment` and `AnnotatedAssignment` (if it has a value),
|
||||
/// `None` for all other definition kinds.
|
||||
pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> Option<&'ast ast::Expr> {
|
||||
match self {
|
||||
DefinitionKind::Assignment(assignment) => Some(assignment.value(module)),
|
||||
DefinitionKind::AnnotatedAssignment(assignment) => assignment.value(module),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::ops::Range;
|
||||
|
||||
use ruff_db::{files::File, parsed::ParsedModuleRef};
|
||||
use ruff_index::newtype_index;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{self as ast, NodeIndex};
|
||||
|
||||
use crate::{
|
||||
Db,
|
||||
@@ -463,6 +463,27 @@ impl NodeWithScopeKind {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the anchor node index for this scope, or `None` for the module scope.
|
||||
///
|
||||
/// This is used to compute relative node indices for expressions within the scope,
|
||||
/// providing a stable anchor that only changes when the scope-introducing node changes.
|
||||
pub(crate) fn node_index(&self) -> Option<NodeIndex> {
|
||||
match self {
|
||||
Self::Module => None,
|
||||
Self::Class(class) => Some(class.index()),
|
||||
Self::ClassTypeParameters(class) => Some(class.index()),
|
||||
Self::Function(function) => Some(function.index()),
|
||||
Self::FunctionTypeParameters(function) => Some(function.index()),
|
||||
Self::TypeAlias(type_alias) => Some(type_alias.index()),
|
||||
Self::TypeAliasTypeParameters(type_alias) => Some(type_alias.index()),
|
||||
Self::Lambda(lambda) => Some(lambda.index()),
|
||||
Self::ListComprehension(comp) => Some(comp.index()),
|
||||
Self::SetComprehension(comp) => Some(comp.index()),
|
||||
Self::DictComprehension(comp) => Some(comp.index()),
|
||||
Self::GeneratorExpression(generator) => Some(generator.index()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -485,6 +485,7 @@ macro_rules! todo_type {
|
||||
pub use crate::types::definition::TypeDefinition;
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_equivalent_visitor,
|
||||
};
|
||||
pub(crate) use todo_type;
|
||||
|
||||
@@ -587,7 +588,7 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default())
|
||||
self.is_equivalent_to_impl(db, other, inferable, &default_equivalent_visitor(db))
|
||||
}
|
||||
|
||||
fn is_equivalent_to_impl(
|
||||
@@ -599,27 +600,27 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let getter_equivalence = if let Some(getter) = self.getter(db) {
|
||||
let Some(other_getter) = other.getter(db) else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
getter.is_equivalent_to_impl(db, other_getter, inferable, visitor)
|
||||
} else {
|
||||
if other.getter(db).is_some() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
};
|
||||
|
||||
let setter_equivalence = || {
|
||||
if let Some(setter) = self.setter(db) {
|
||||
let Some(other_setter) = other.setter(db) else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
setter.is_equivalent_to_impl(db, other_setter, inferable, visitor)
|
||||
} else {
|
||||
if other.setter(db).is_some() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4111,55 +4112,6 @@ impl<'db> Type<'db> {
|
||||
.into()
|
||||
}
|
||||
|
||||
Some(KnownClass::Type) => {
|
||||
let str_instance = KnownClass::Str.to_instance(db);
|
||||
let type_instance = KnownClass::Type.to_instance(db);
|
||||
|
||||
// ```py
|
||||
// class type:
|
||||
// @overload
|
||||
// def __init__(self, o: object, /) -> None: ...
|
||||
// @overload
|
||||
// def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ...
|
||||
// ```
|
||||
CallableBinding::from_overloads(
|
||||
self,
|
||||
[
|
||||
Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[Parameter::positional_only(Some(Name::new_static("o")))
|
||||
.with_annotated_type(Type::any())],
|
||||
),
|
||||
type_instance,
|
||||
),
|
||||
Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[
|
||||
Parameter::positional_only(Some(Name::new_static("name")))
|
||||
.with_annotated_type(str_instance),
|
||||
Parameter::positional_only(Some(Name::new_static("bases")))
|
||||
.with_annotated_type(Type::homogeneous_tuple(
|
||||
db,
|
||||
type_instance,
|
||||
)),
|
||||
Parameter::positional_only(Some(Name::new_static("dict")))
|
||||
.with_annotated_type(
|
||||
KnownClass::Dict.to_specialized_instance(
|
||||
db,
|
||||
&[str_instance, Type::any()],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
type_instance,
|
||||
),
|
||||
],
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
Some(KnownClass::Object) => {
|
||||
// ```py
|
||||
// class object:
|
||||
@@ -6559,9 +6511,9 @@ impl<'db> Type<'db> {
|
||||
Some(TypeDefinition::Function(function.definition(db)))
|
||||
}
|
||||
Self::ModuleLiteral(module) => Some(TypeDefinition::Module(module.module(db))),
|
||||
Self::ClassLiteral(class_literal) => Some(class_literal.type_definition(db)),
|
||||
Self::ClassLiteral(class_literal) => class_literal.type_definition(db),
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::StaticClass(alias.definition(db))),
|
||||
Self::NominalInstance(instance) => Some(instance.class(db).type_definition(db)),
|
||||
Self::NominalInstance(instance) => instance.class(db).type_definition(db),
|
||||
Self::KnownInstance(instance) => match instance {
|
||||
KnownInstanceType::TypeVar(var) => {
|
||||
Some(TypeDefinition::TypeVar(var.definition(db)?))
|
||||
@@ -6575,7 +6527,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => None,
|
||||
SubclassOfInner::Class(class) => Some(class.type_definition(db)),
|
||||
SubclassOfInner::Class(class) => class.type_definition(db),
|
||||
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||
Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?))
|
||||
}
|
||||
@@ -6605,7 +6557,7 @@ impl<'db> Type<'db> {
|
||||
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
|
||||
Self::ProtocolInstance(protocol) => match protocol.inner {
|
||||
Protocol::FromClass(class) => Some(class.type_definition(db)),
|
||||
Protocol::FromClass(class) => class.type_definition(db),
|
||||
Protocol::Synthesized(_) => None,
|
||||
},
|
||||
|
||||
@@ -10555,7 +10507,7 @@ impl<'db> CallableType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if other.is_function_like(db) && !self.is_function_like(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
self.signatures(db).has_relation_to_impl(
|
||||
@@ -10579,13 +10531,20 @@ impl<'db> CallableType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || {
|
||||
self.signatures(db)
|
||||
.is_equivalent_to_impl(db, other.signatures(db), inferable, visitor)
|
||||
})
|
||||
ConstraintSet::from_bool(db, self.is_function_like(db) == other.is_function_like(db)).and(
|
||||
db,
|
||||
|| {
|
||||
self.signatures(db).is_equivalent_to_impl(
|
||||
db,
|
||||
other.signatures(db),
|
||||
inferable,
|
||||
visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10770,7 +10729,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
) => self_property.when_equivalent_to(db, other_property, inferable),
|
||||
|
||||
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => {
|
||||
ConstraintSet::from(self == other)
|
||||
ConstraintSet::from_bool(db, self == other)
|
||||
}
|
||||
|
||||
(
|
||||
@@ -10800,7 +10759,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| (
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from(true),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
@@ -10827,7 +10786,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetSatisfies(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
|
||||
| KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from(false),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10859,7 +10818,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
) => self_property.is_equivalent_to_impl(db, other_property, inferable, visitor),
|
||||
|
||||
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => {
|
||||
ConstraintSet::from(self == other)
|
||||
ConstraintSet::from_bool(db, self == other)
|
||||
}
|
||||
|
||||
(
|
||||
@@ -10873,7 +10832,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| (
|
||||
KnownBoundMethodType::ConstraintSetNever,
|
||||
KnownBoundMethodType::ConstraintSetNever,
|
||||
) => ConstraintSet::from(true),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints),
|
||||
@@ -10893,7 +10852,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
(
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(left_generic_context),
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(right_generic_context),
|
||||
) => ConstraintSet::from(left_generic_context == right_generic_context),
|
||||
) => ConstraintSet::from_bool(db, left_generic_context == right_generic_context),
|
||||
|
||||
(
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||
@@ -10920,7 +10879,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
| KnownBoundMethodType::ConstraintSetSatisfies(_)
|
||||
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
|
||||
| KnownBoundMethodType::GenericContextSpecializeConstrained(_),
|
||||
) => ConstraintSet::from(false),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12120,23 +12079,23 @@ impl<'db> UnionType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
let self_elements = self.elements(db);
|
||||
let other_elements = other.elements(db);
|
||||
|
||||
if self_elements.len() != other_elements.len() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == Type::Union(other) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
ConstraintSet::from(sorted_self == other.normalized(db))
|
||||
ConstraintSet::from_bool(db, sorted_self == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Identify some specific unions of known classes, currently the ones that `float` and
|
||||
@@ -12530,30 +12489,30 @@ impl<'db> IntersectionType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
let self_positive = self.positive(db);
|
||||
let other_positive = other.positive(db);
|
||||
|
||||
if self_positive.len() != other_positive.len() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let self_negative = self.negative(db);
|
||||
let other_negative = other.negative(db);
|
||||
|
||||
if self_negative.len() != other_negative.len() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
ConstraintSet::from(sorted_self == other.normalized(db))
|
||||
ConstraintSet::from_bool(db, sorted_self == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Returns an iterator over the positive elements of the intersection. If
|
||||
|
||||
@@ -1313,7 +1313,7 @@ impl<'db> Bindings<'db> {
|
||||
if !overload.parameter_types().is_empty() {
|
||||
return;
|
||||
}
|
||||
let constraints = ConstraintSet::from(true);
|
||||
let constraints = ConstraintSet::from_bool(db, true);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(
|
||||
KnownInstanceType::ConstraintSet(tracked),
|
||||
@@ -1324,7 +1324,7 @@ impl<'db> Bindings<'db> {
|
||||
if !overload.parameter_types().is_empty() {
|
||||
return;
|
||||
}
|
||||
let constraints = ConstraintSet::from(false);
|
||||
let constraints = ConstraintSet::from_bool(db, false);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(
|
||||
KnownInstanceType::ConstraintSet(tracked),
|
||||
@@ -1443,12 +1443,6 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownClass::Type) if overload_index == 0 => {
|
||||
if let [Some(arg)] = overload.parameter_types() {
|
||||
overload.set_return_type(arg.dunder_class(db));
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownClass::Property) => {
|
||||
if let [getter, setter, ..] = overload.parameter_types() {
|
||||
overload.set_return_type(Type::PropertyInstance(
|
||||
|
||||
@@ -37,6 +37,7 @@ use crate::types::member::{Member, class_member};
|
||||
use crate::types::mro::{DynamicMroError, DynamicMroErrorKind};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
@@ -60,7 +61,7 @@ use crate::{
|
||||
attribute_assignments,
|
||||
definition::{DefinitionKind, TargetKind},
|
||||
place_table,
|
||||
scope::{FileScopeId, ScopeId},
|
||||
scope::ScopeId,
|
||||
semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
@@ -74,7 +75,7 @@ use ruff_db::diagnostic::Span;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{self as ast, PythonVersion};
|
||||
use ruff_python_ast::{self as ast, NodeIndex, PythonVersion};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_module_resolver::{KnownModule, file_to_module};
|
||||
@@ -562,25 +563,16 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
|
||||
/// Returns the generic context if this is a generic class.
|
||||
///
|
||||
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
|
||||
// to inherit from `Generic[T]`, since dynamic classes can't be generic. See also: `is_protocol`.
|
||||
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
|
||||
self.as_static().and_then(|class| class.generic_context(db))
|
||||
}
|
||||
|
||||
/// Returns whether this class is a protocol.
|
||||
///
|
||||
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
|
||||
// to inherit from `Protocol`, since dynamic classes can't be protocols. See also: `generic_context`.
|
||||
pub(crate) fn is_protocol(self, db: &'db dyn Db) -> bool {
|
||||
self.as_static().is_some_and(|class| class.is_protocol(db))
|
||||
}
|
||||
|
||||
/// Returns whether this class is a `TypedDict`.
|
||||
// TODO: We should emit a diagnostic if a dynamic class (created via `type()`) attempts
|
||||
// to inherit from `TypedDict`. To create a dynamic TypedDict, you should invoke
|
||||
// `TypedDict` as a function, not `type`. See also: `generic_context`, `is_protocol`.
|
||||
pub fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Self::Static(class) => class.is_typed_dict(db),
|
||||
@@ -613,7 +605,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
pub(crate) fn file(self, db: &dyn Db) -> File {
|
||||
match self {
|
||||
Self::Static(class) => class.file(db),
|
||||
Self::Dynamic(class) => class.file(db),
|
||||
Self::Dynamic(class) => class.scope(db).file(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,10 +626,12 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
|
||||
/// Returns whether this class is final.
|
||||
// TODO: Support `@final` on dynamic classes, e.g. `X = final(type("X", (), {}))`.
|
||||
// We should either recognize and track this, or emit a diagnostic if unsupported.
|
||||
pub(crate) fn is_final(self, db: &'db dyn Db) -> bool {
|
||||
self.as_static().is_some_and(|class| class.is_final(db))
|
||||
match self {
|
||||
Self::Static(class) => class.is_final(db),
|
||||
// Dynamic classes created via `type()` cannot be marked as final.
|
||||
Self::Dynamic(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this class defines any ordering method (`__lt__`, `__le__`, `__gt__`,
|
||||
@@ -664,10 +658,10 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the definition of this class.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
/// Returns the definition of this class, if available.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self {
|
||||
Self::Static(class) => class.definition(db),
|
||||
Self::Static(class) => Some(class.definition(db)),
|
||||
Self::Dynamic(class) => class.definition(db),
|
||||
}
|
||||
}
|
||||
@@ -675,11 +669,11 @@ impl<'db> ClassLiteral<'db> {
|
||||
/// Returns the type definition for this class.
|
||||
///
|
||||
/// For static classes, returns `TypeDefinition::StaticClass`.
|
||||
/// For dynamic classes, returns `TypeDefinition::DynamicClass`.
|
||||
pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
|
||||
/// For dynamic classes, returns `TypeDefinition::DynamicClass` if a definition is available.
|
||||
pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
|
||||
match self {
|
||||
Self::Static(class) => TypeDefinition::StaticClass(class.definition(db)),
|
||||
Self::Dynamic(class) => TypeDefinition::DynamicClass(class.definition(db)),
|
||||
Self::Static(class) => Some(TypeDefinition::StaticClass(class.definition(db))),
|
||||
Self::Dynamic(class) => class.definition(db).map(TypeDefinition::DynamicClass),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,13 +938,13 @@ impl<'db> ClassType<'db> {
|
||||
self.class_literal(db).known(db)
|
||||
}
|
||||
|
||||
/// Returns the definition for this class.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
/// Returns the definition for this class, if available.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
self.class_literal(db).definition(db)
|
||||
}
|
||||
|
||||
/// Returns the type definition for this class.
|
||||
pub(crate) fn type_definition(self, db: &'db dyn Db) -> TypeDefinition<'db> {
|
||||
pub(crate) fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
|
||||
self.class_literal(db).type_definition(db)
|
||||
}
|
||||
|
||||
@@ -1070,8 +1064,8 @@ impl<'db> ClassType<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1090,42 +1084,45 @@ impl<'db> ClassType<'db> {
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::SubtypingAssuming(_) => {
|
||||
ConstraintSet::from(other.is_object(db))
|
||||
ConstraintSet::from_bool(db, other.is_object(db))
|
||||
}
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
|
||||
ConstraintSet::from(!other.is_final(db))
|
||||
ConstraintSet::from_bool(db, !other.is_final(db))
|
||||
}
|
||||
},
|
||||
|
||||
// Protocol, Generic, and TypedDict are special bases that don't match ClassType.
|
||||
ClassBase::Protocol | ClassBase::Generic | ClassBase::TypedDict => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
ClassBase::Class(base) => match (base, other) {
|
||||
// Two non-generic classes match if they have the same class literal.
|
||||
(ClassType::NonGeneric(base_literal), ClassType::NonGeneric(other_literal)) => {
|
||||
ConstraintSet::from(base_literal == other_literal)
|
||||
ConstraintSet::from_bool(db, base_literal == other_literal)
|
||||
}
|
||||
|
||||
// Two generic classes match if they have the same origin and compatible specializations.
|
||||
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
||||
ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || {
|
||||
base.specialization(db).has_relation_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
ConstraintSet::from_bool(db, base.origin(db) == other.origin(db)).and(
|
||||
db,
|
||||
|| {
|
||||
base.specialization(db).has_relation_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Generic and non-generic classes don't match.
|
||||
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1140,18 +1137,18 @@ impl<'db> ClassType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
// Two non-generic classes are only equivalent if they are equal (handled above).
|
||||
// A non-generic class is never equivalent to a generic class.
|
||||
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||
ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || {
|
||||
ConstraintSet::from_bool(db, this.origin(db) == other.origin(db)).and(db, || {
|
||||
this.specialization(db).is_equivalent_to_impl(
|
||||
db,
|
||||
other.specialization(db),
|
||||
@@ -4704,12 +4701,8 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
|
||||
///
|
||||
/// # Salsa interning
|
||||
///
|
||||
/// Each `type()` call is uniquely identified by its [`Definition`], which provides
|
||||
/// stable identity without depending on AST node indices that can change when code
|
||||
/// is inserted above the call site.
|
||||
///
|
||||
/// Two different `type()` calls always produce distinct `DynamicClassLiteral`
|
||||
/// instances, even if they have the same name and bases:
|
||||
/// This is a Salsa-interned struct. Two different `type()` calls always produce
|
||||
/// distinct `DynamicClassLiteral` instances, even if they have the same name and bases:
|
||||
///
|
||||
/// ```python
|
||||
/// Foo1 = type("Foo", (Base,), {})
|
||||
@@ -4717,9 +4710,11 @@ impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
|
||||
/// # Foo1 and Foo2 are distinct types
|
||||
/// ```
|
||||
///
|
||||
/// Note: Only assigned `type()` calls are currently supported (e.g., `Foo = type(...)`).
|
||||
/// Inline calls like `process(type(...))` fall back to normal call handling.
|
||||
#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
|
||||
/// The `anchor` field provides stable identity:
|
||||
/// - For assigned `type()` calls, the `Definition` uniquely identifies the class.
|
||||
/// - For dangling `type()` calls, a relative node offset anchored to the enclosing scope
|
||||
/// provides stable identity that only changes when the scope itself changes.
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct DynamicClassLiteral<'db> {
|
||||
/// The name of the class (from the first argument to `type()`).
|
||||
@@ -4730,8 +4725,13 @@ pub struct DynamicClassLiteral<'db> {
|
||||
#[returns(deref)]
|
||||
pub bases: Box<[ClassBase<'db>]>,
|
||||
|
||||
/// The definition where this class is created.
|
||||
pub definition: Definition<'db>,
|
||||
/// The anchor for this dynamic class, providing stable identity.
|
||||
///
|
||||
/// - `Definition`: The `type()` call is assigned to a variable. The definition
|
||||
/// uniquely identifies this class and can be used to find the `type()` call.
|
||||
/// - `ScopeOffset`: The `type()` call is "dangling" (not assigned). The offset
|
||||
/// is relative to the enclosing scope's anchor node index.
|
||||
pub anchor: DynamicClassAnchor<'db>,
|
||||
|
||||
/// The class members from the namespace dict (third argument to `type()`).
|
||||
/// Each entry is a (name, type) pair extracted from the dict literal.
|
||||
@@ -4748,38 +4748,87 @@ pub struct DynamicClassLiteral<'db> {
|
||||
pub dataclass_params: Option<DataclassParams<'db>>,
|
||||
}
|
||||
|
||||
/// Anchor for identifying a dynamic class literal.
|
||||
///
|
||||
/// This enum provides stable identity for `DynamicClassLiteral`:
|
||||
/// - For assigned calls, the `Definition` uniquely identifies the class.
|
||||
/// - For dangling calls, a relative offset provides stable identity.
|
||||
#[derive(
|
||||
Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, salsa::Update, get_size2::GetSize,
|
||||
)]
|
||||
pub enum DynamicClassAnchor<'db> {
|
||||
/// The `type()` call is assigned to a variable.
|
||||
///
|
||||
/// The `Definition` uniquely identifies this class. The `type()` call expression
|
||||
/// is the `value` of the assignment, so we can get its range from the definition.
|
||||
Definition(Definition<'db>),
|
||||
|
||||
/// The `type()` call is "dangling" (not assigned to a variable).
|
||||
///
|
||||
/// The offset is relative to the enclosing scope's anchor node index.
|
||||
/// For module scope, this is equivalent to an absolute index (anchor is 0).
|
||||
ScopeOffset { scope: ScopeId<'db>, offset: u32 },
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for DynamicClassLiteral<'_> {}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> DynamicClassLiteral<'db> {
|
||||
/// Returns the definition where this class is created, if it was assigned to a variable.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => Some(definition),
|
||||
DynamicClassAnchor::ScopeOffset { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the scope in which this dynamic class was created.
|
||||
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => definition.scope(db),
|
||||
DynamicClassAnchor::ScopeOffset { scope, .. } => scope,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`Span`] with the range of the `type()` call expression.
|
||||
///
|
||||
/// See [`Self::header_range`] for more details.
|
||||
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
|
||||
Span::from(self.file(db)).with_range(self.header_range(db))
|
||||
Span::from(self.scope(db).file(db)).with_range(self.header_range(db))
|
||||
}
|
||||
|
||||
/// Returns the range of the `type()` call expression that created this class.
|
||||
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
||||
let definition = self.definition(db);
|
||||
let file = definition.file(db);
|
||||
let scope = self.scope(db);
|
||||
let file = scope.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
|
||||
// Dynamic classes are only created from regular assignments (e.g., `Foo = type(...)`).
|
||||
let DefinitionKind::Assignment(assignment) = definition.kind(db) else {
|
||||
unreachable!("DynamicClassLiteral should only be created from Assignment definitions");
|
||||
};
|
||||
assignment.value(&module).range()
|
||||
}
|
||||
match self.anchor(db) {
|
||||
DynamicClassAnchor::Definition(definition) => {
|
||||
// For definitions, get the range from the definition's value.
|
||||
// The `type()` call is the value of the assignment.
|
||||
definition
|
||||
.kind(db)
|
||||
.value(&module)
|
||||
.expect("DynamicClassAnchor::Definition should only be used for assignments")
|
||||
.range()
|
||||
}
|
||||
DynamicClassAnchor::ScopeOffset { offset, .. } => {
|
||||
// For dangling `type()` calls, compute the absolute index from the offset.
|
||||
let scope_anchor = scope.node(db).node_index().unwrap_or(NodeIndex::from(0));
|
||||
let anchor_u32 = scope_anchor
|
||||
.as_u32()
|
||||
.expect("anchor should not be NodeIndex::NONE");
|
||||
let absolute_index = NodeIndex::from(anchor_u32 + offset);
|
||||
|
||||
/// Returns the file containing the `type()` call.
|
||||
pub(crate) fn file(self, db: &'db dyn Db) -> File {
|
||||
self.definition(db).file(db)
|
||||
}
|
||||
|
||||
/// Returns the scope containing the `type()` call.
|
||||
pub(crate) fn file_scope(self, db: &'db dyn Db) -> FileScopeId {
|
||||
self.definition(db).file_scope(db)
|
||||
// Get the node and return its range.
|
||||
let node: &ast::ExprCall = module
|
||||
.get_by_index(absolute_index)
|
||||
.try_into()
|
||||
.expect("scope offset should point to ExprCall");
|
||||
node.range()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the metaclass of this dynamic class.
|
||||
@@ -5020,7 +5069,7 @@ impl<'db> DynamicClassLiteral<'db> {
|
||||
db,
|
||||
self.name(db).clone(),
|
||||
self.bases(db),
|
||||
self.definition(db),
|
||||
self.anchor(db),
|
||||
self.members(db),
|
||||
self.has_dynamic_namespace(db),
|
||||
dataclass_params,
|
||||
@@ -5314,7 +5363,8 @@ impl<'db> QualifiedClassName<'db> {
|
||||
}
|
||||
ClassLiteral::Dynamic(class) => {
|
||||
// Dynamic classes don't have a body scope; start from the enclosing scope.
|
||||
(class.file(self.db), class.file_scope(self.db), 0)
|
||||
let scope = class.scope(self.db);
|
||||
(scope.file(self.db), scope.file_scope_id(self.db), 0)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6477,7 +6527,7 @@ impl KnownClass {
|
||||
db: &'db dyn Db,
|
||||
other: ClassType<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from(self.is_subclass_of(db, other))
|
||||
ConstraintSet::from_bool(db, self.is_subclass_of(db, other))
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -104,6 +104,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
|
||||
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
|
||||
registry.register_lint(&OVERRIDE_OF_FINAL_METHOD);
|
||||
registry.register_lint(&INEFFECTIVE_FINAL);
|
||||
registry.register_lint(&TYPE_ASSERTION_FAILURE);
|
||||
registry.register_lint(&TOO_MANY_POSITIONAL_ARGUMENTS);
|
||||
registry.register_lint(&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS);
|
||||
@@ -1789,6 +1790,34 @@ declare_lint! {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for calls to `final()` that type checkers cannot interpret.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `final()` function is designed to be used as a decorator. When called directly
|
||||
/// as a function (e.g., `final(type(...))`), type checkers will not understand the
|
||||
/// application of `final` and will not prevent subclassing.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// from typing import final
|
||||
///
|
||||
/// # Incorrect: type checkers will not prevent subclassing
|
||||
/// MyClass = final(type("MyClass", (), {}))
|
||||
///
|
||||
/// # Correct: use `final` as a decorator
|
||||
/// @final
|
||||
/// class MyClass: ...
|
||||
/// ```
|
||||
pub(crate) static INEFFECTIVE_FINAL = {
|
||||
summary: "detects calls to `final()` that type checkers cannot interpret",
|
||||
status: LintStatus::preview("0.0.1-alpha.33"),
|
||||
default_level: Level::Warn,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for methods that are decorated with `@override` but do not override any method in a superclass.
|
||||
|
||||
@@ -67,7 +67,6 @@ pub(crate) fn enum_metadata<'db>(
|
||||
//
|
||||
// MyEnum = type("MyEnum", (BaseEnum,), {"A": 1, "B": 2})
|
||||
// ```
|
||||
// TODO: Add a diagnostic for including an enum in a `type(...)` call.
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1123,7 +1123,7 @@ impl<'db> FunctionType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.literal(db) != other.literal(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let self_signature = self.signature(db);
|
||||
@@ -1146,10 +1146,10 @@ impl<'db> FunctionType<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.normalized(db) == other.normalized(db) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
if self.literal(db) != other.literal(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
let self_signature = self.signature(db);
|
||||
let other_signature = other.signature(db);
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::signatures::Parameters;
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
@@ -96,7 +97,7 @@ pub(crate) fn typing_self<'db>(
|
||||
let identity = TypeVarIdentity::new(
|
||||
db,
|
||||
ast::name::Name::new_static("Self"),
|
||||
Some(class.definition(db)),
|
||||
class.definition(db),
|
||||
TypeVarKind::TypingSelf,
|
||||
);
|
||||
let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance(
|
||||
@@ -793,12 +794,12 @@ fn is_subtype_in_invariant_position<'db>(
|
||||
// This should be removed and properly handled in the respective
|
||||
// `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of
|
||||
// `Type::has_relation_to_impl`. Right now, we cannot generally
|
||||
// return `ConstraintSet::from(true)` from that branch, as that
|
||||
// return `ConstraintSet::from_bool(db, true)` from that branch, as that
|
||||
// leads to union simplification, which means that we lose track
|
||||
// of type variables without recording the constraints under which
|
||||
// the relation holds.
|
||||
if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
derived.has_relation_to_impl(
|
||||
@@ -1272,7 +1273,7 @@ impl<'db> Specialization<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
@@ -1331,7 +1332,7 @@ impl<'db> Specialization<'db> {
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(true),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, true),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1346,8 +1347,8 @@ impl<'db> Specialization<'db> {
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
&IsDisjointVisitor::default(),
|
||||
&HasRelationToVisitor::default(),
|
||||
&default_disjoint_visitor(db),
|
||||
&default_relation_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1361,7 +1362,7 @@ impl<'db> Specialization<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
@@ -1399,13 +1400,13 @@ impl<'db> Specialization<'db> {
|
||||
),
|
||||
|
||||
// If `Foo[T]` is covariant in `T`, `Foo[Never]` is a subtype of `Foo[A]` and `Foo[B]`
|
||||
TypeVarVariance::Covariant => ConstraintSet::from(false),
|
||||
TypeVarVariance::Covariant => ConstraintSet::from_bool(db, false),
|
||||
|
||||
// If `Foo[T]` is contravariant in `T`, `Foo[A | B]` is a subtype of `Foo[A]` and `Foo[B]`
|
||||
TypeVarVariance::Contravariant => ConstraintSet::from(false),
|
||||
TypeVarVariance::Contravariant => ConstraintSet::from_bool(db, false),
|
||||
|
||||
// If `Foo[T]` is bivariant in `T`, `Foo[A]` and `Foo[B]` are mutual subtypes.
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(false),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, false),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1418,14 +1419,14 @@ impl<'db> Specialization<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.materialization_kind(db) != other.materialization_kind(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
for ((bound_typevar, self_type), other_type) in generic_context
|
||||
.variables(db)
|
||||
.zip(self.types(db))
|
||||
@@ -1443,7 +1444,7 @@ impl<'db> Specialization<'db> {
|
||||
| TypeVarVariance::Contravariant => {
|
||||
self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor)
|
||||
}
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(true),
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from_bool(db, true),
|
||||
};
|
||||
if result.intersect(db, compatible).is_never_satisfied(db) {
|
||||
return result;
|
||||
@@ -1451,7 +1452,7 @@ impl<'db> Specialization<'db> {
|
||||
}
|
||||
|
||||
match (self.tuple_inner(db), other.tuple_inner(db)) {
|
||||
(Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false),
|
||||
(Some(_), None) | (None, Some(_)) => return ConstraintSet::from_bool(db, false),
|
||||
(None, None) => {}
|
||||
(Some(self_tuple), Some(other_tuple)) => {
|
||||
let compatible =
|
||||
|
||||
@@ -169,9 +169,9 @@ pub fn definitions_for_name<'db>(
|
||||
// instead of `int` (hover only shows the docstring of the first definition).
|
||||
.rev()
|
||||
.filter_map(|ty| ty.as_nominal_instance())
|
||||
.map(|instance| {
|
||||
let definition = instance.class_literal(db).definition(db);
|
||||
ResolvedDefinition::Definition(definition)
|
||||
.filter_map(|instance| {
|
||||
let definition = instance.class_literal(db).definition(db)?;
|
||||
Some(ResolvedDefinition::Definition(definition))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
@@ -55,21 +55,22 @@ use crate::subscript::{PyIndex, PySlice};
|
||||
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
|
||||
use crate::types::call::{Argument, Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
||||
use crate::types::class::{
|
||||
ClassLiteral, CodeGeneratorKind, DynamicClassLiteral, DynamicMetaclassConflict, FieldKind,
|
||||
MetaclassErrorKind, MethodDecorator,
|
||||
ClassLiteral, CodeGeneratorKind, DynamicClassAnchor, DynamicClassLiteral,
|
||||
DynamicMetaclassConflict, FieldKind, MetaclassErrorKind, MethodDecorator,
|
||||
};
|
||||
use crate::types::context::{InNoTypeCheck, InferContext};
|
||||
use crate::types::cyclic::CycleDetector;
|
||||
use crate::types::diagnostic::{
|
||||
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||
CYCLIC_CLASS_DEFINITION, CYCLIC_TYPE_ALIAS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE,
|
||||
DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT,
|
||||
INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_GENERIC_CLASS,
|
||||
INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
|
||||
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM,
|
||||
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
|
||||
DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE,
|
||||
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
|
||||
INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE,
|
||||
INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS,
|
||||
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases,
|
||||
NO_MATCHING_OVERLOAD, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
|
||||
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
|
||||
TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
|
||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR,
|
||||
@@ -5581,10 +5582,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// Try to extract the dynamic class with definition.
|
||||
// This returns `None` if it's not a three-arg call to `type()`,
|
||||
// signalling that we must fall back to normal call inference.
|
||||
self.infer_dynamic_type_expression(call_expr, definition)
|
||||
.unwrap_or_else(|| {
|
||||
self.infer_call_expression_impl(call_expr, callable_type, tcx)
|
||||
})
|
||||
self.infer_builtins_type_call(call_expr, Some(definition))
|
||||
}
|
||||
Some(_) | None => {
|
||||
self.infer_call_expression_impl(call_expr, callable_type, tcx)
|
||||
@@ -6189,19 +6187,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to infer a 3-argument `type(name, bases, dict)` call expression, capturing the definition.
|
||||
/// Infer a call to `builtins.type()`.
|
||||
///
|
||||
/// This is called when we detect a `type()` call in assignment context and want to
|
||||
/// associate the resulting `DynamicClassLiteral` with its definition for go-to-definition.
|
||||
/// `builtins.type` has two overloads: a single-argument overload (e.g. `type("foo")`,
|
||||
/// and a 3-argument `type(name, bases, dict)` overload. Both are handled here.
|
||||
/// The `definition` parameter should be `Some()` if this call to `builtins.type()`
|
||||
/// occurs on the right-hand side of an assignment statement that has a [`Definition`]
|
||||
/// associated with it in the semantic index.
|
||||
///
|
||||
/// Returns `None` if any keywords were provided or the number of arguments is not three,
|
||||
/// signalling that no types were stored for any AST sub-expressions and that we should
|
||||
/// therefore fallback to normal call binding for error reporting.
|
||||
fn infer_dynamic_type_expression(
|
||||
/// If it's unclear which overload we should pick, we return `type[Unknown]`,
|
||||
/// to avoid cascading errors later on.
|
||||
fn infer_builtins_type_call(
|
||||
&mut self,
|
||||
call_expr: &ast::ExprCall,
|
||||
definition: Definition<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
definition: Option<Definition<'db>>,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
|
||||
let ast::Arguments {
|
||||
@@ -6211,27 +6211,101 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
node_index: _,
|
||||
} = &call_expr.arguments;
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
for keyword in keywords {
|
||||
self.infer_expression(&keyword.value, TypeContext::default());
|
||||
}
|
||||
|
||||
let [name_arg, bases_arg, namespace_arg] = &**args else {
|
||||
return None;
|
||||
let [name_arg, bases_arg, namespace_arg] = match &**args {
|
||||
[single] => {
|
||||
let arg_type = self.infer_expression(single, TypeContext::default());
|
||||
|
||||
return if keywords.is_empty() {
|
||||
arg_type.dunder_class(db)
|
||||
} else {
|
||||
if keywords.iter().any(|keyword| keyword.arg.is_some())
|
||||
&& let Some(builder) =
|
||||
self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr)
|
||||
{
|
||||
let mut diagnostic = builder
|
||||
.into_diagnostic("No overload of class `type` matches arguments");
|
||||
diagnostic.help(format_args!(
|
||||
"`builtins.type()` expects no keyword arguments",
|
||||
));
|
||||
}
|
||||
SubclassOfType::subclass_of_unknown()
|
||||
};
|
||||
}
|
||||
|
||||
[first, second] if second.is_starred_expr() => {
|
||||
self.infer_expression(first, TypeContext::default());
|
||||
self.infer_expression(second, TypeContext::default());
|
||||
|
||||
match &**keywords {
|
||||
[single] if single.arg.is_none() => {
|
||||
return SubclassOfType::subclass_of_unknown();
|
||||
}
|
||||
_ => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr)
|
||||
{
|
||||
let mut diagnostic = builder
|
||||
.into_diagnostic("No overload of class `type` matches arguments");
|
||||
diagnostic.help(format_args!(
|
||||
"`builtins.type()` expects no keyword arguments",
|
||||
));
|
||||
}
|
||||
|
||||
return SubclassOfType::subclass_of_unknown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[name, bases, namespace] => [name, bases, namespace],
|
||||
|
||||
_ => {
|
||||
for arg in args {
|
||||
self.infer_expression(arg, TypeContext::default());
|
||||
}
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr) {
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("No overload of class `type` matches arguments");
|
||||
diagnostic.help(format_args!(
|
||||
"`builtins.type()` can either be called with one or three \
|
||||
positional arguments (got {})",
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
|
||||
return SubclassOfType::subclass_of_unknown();
|
||||
}
|
||||
};
|
||||
|
||||
// If any argument is a starred expression, we can't know how many positional arguments
|
||||
// we're receiving, so fall back to normal call binding.
|
||||
if args.iter().any(ast::Expr::is_starred_expr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Infer the argument types.
|
||||
let name_type = self.infer_expression(name_arg, TypeContext::default());
|
||||
let bases_type = self.infer_expression(bases_arg, TypeContext::default());
|
||||
let namespace_type = self.infer_expression(namespace_arg, TypeContext::default());
|
||||
|
||||
// TODO: validate other keywords against `__init_subclass__` methods of superclasses
|
||||
if keywords
|
||||
.iter()
|
||||
.filter_map(|keyword| keyword.arg.as_deref())
|
||||
.contains("metaclass")
|
||||
{
|
||||
if let Some(builder) = self.context.report_lint(&NO_MATCHING_OVERLOAD, call_expr) {
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("No overload of class `type` matches arguments");
|
||||
diagnostic
|
||||
.help("The `metaclass` keyword argument is not supported in `type()` calls");
|
||||
}
|
||||
}
|
||||
|
||||
// If any argument is a starred expression, we can't know how many positional arguments
|
||||
// we're receiving, so fall back to `type[Unknown]` to avoid false-positive errors.
|
||||
if args.iter().any(ast::Expr::is_starred_expr) {
|
||||
return SubclassOfType::subclass_of_unknown();
|
||||
}
|
||||
|
||||
// Extract members from the namespace dict (third argument).
|
||||
// Infer the whole dict first to avoid double-inferring individual values.
|
||||
let namespace_type = self.infer_expression(namespace_arg, TypeContext::default());
|
||||
let (members, has_dynamic_namespace): (Box<[(ast::name::Name, Type<'db>)]>, bool) =
|
||||
if let ast::Expr::Dict(dict) = namespace_arg {
|
||||
// Check if all keys are string literal types. If any key is not a string literal
|
||||
@@ -6258,7 +6332,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.collect();
|
||||
(members, !all_keys_are_string_literals)
|
||||
} else if let Type::TypedDict(typed_dict) = namespace_type {
|
||||
// Namespace is a TypedDict instance. Extract known keys as members.
|
||||
// `namespace` is a TypedDict instance. Extract known keys as members.
|
||||
// TypedDicts are "open" (can have additional string keys), so this
|
||||
// is still a dynamic namespace for unknown attributes.
|
||||
let members: Box<[(ast::name::Name, Type<'db>)]> = typed_dict
|
||||
@@ -6268,7 +6342,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.collect();
|
||||
(members, true)
|
||||
} else {
|
||||
// Namespace is not a dict literal, so it's dynamic.
|
||||
// `namespace` is not a dict literal, so it's dynamic.
|
||||
(Box::new([]), true)
|
||||
};
|
||||
|
||||
@@ -6310,11 +6384,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let (bases, mut disjoint_bases) =
|
||||
self.extract_dynamic_type_bases(bases_arg, bases_type, &name);
|
||||
|
||||
let scope = self.scope();
|
||||
|
||||
// Create the anchor for identifying this dynamic class.
|
||||
// - For assigned `type()` calls, the Definition uniquely identifies the class.
|
||||
// - For dangling calls, compute a relative offset from the scope's node index.
|
||||
let anchor = if let Some(def) = definition {
|
||||
DynamicClassAnchor::Definition(def)
|
||||
} else {
|
||||
let call_node_index = call_expr.node_index().load();
|
||||
let scope_anchor = scope.node(db).node_index().unwrap_or(NodeIndex::from(0));
|
||||
let anchor_u32 = scope_anchor
|
||||
.as_u32()
|
||||
.expect("scope anchor should not be NodeIndex::NONE");
|
||||
let call_u32 = call_node_index
|
||||
.as_u32()
|
||||
.expect("call node should not be NodeIndex::NONE");
|
||||
DynamicClassAnchor::ScopeOffset {
|
||||
scope,
|
||||
offset: call_u32 - anchor_u32,
|
||||
}
|
||||
};
|
||||
|
||||
let dynamic_class = DynamicClassLiteral::new(
|
||||
db,
|
||||
name,
|
||||
bases,
|
||||
definition,
|
||||
anchor,
|
||||
members,
|
||||
has_dynamic_namespace,
|
||||
None,
|
||||
@@ -6384,7 +6480,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
);
|
||||
}
|
||||
|
||||
Some(Type::ClassLiteral(ClassLiteral::Dynamic(dynamic_class)))
|
||||
Type::ClassLiteral(ClassLiteral::Dynamic(dynamic_class))
|
||||
}
|
||||
|
||||
/// Extract base classes from the second argument of a `type()` call.
|
||||
@@ -6421,26 +6517,129 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, base)| {
|
||||
let diagnostic_node = bases_tuple_elts
|
||||
.and_then(|elts| elts.get(idx))
|
||||
.unwrap_or(bases_node);
|
||||
|
||||
// First try the standard conversion.
|
||||
if let Some(class_base) =
|
||||
ClassBase::try_from_type(db, *base, placeholder_class)
|
||||
{
|
||||
// Collect disjoint bases for instance-layout-conflict checking.
|
||||
if let ClassBase::Class(base_class) = class_base {
|
||||
if let Some(disjoint_base) = base_class.nearest_disjoint_base(db) {
|
||||
disjoint_bases.insert(
|
||||
disjoint_base,
|
||||
idx,
|
||||
base_class.class_literal(db),
|
||||
);
|
||||
// Check for special bases that are not allowed for dynamic classes.
|
||||
// Dynamic classes can't be generic, protocols, TypedDicts, or enums.
|
||||
match class_base {
|
||||
ClassBase::Generic | ClassBase::TypedDict => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_BASE, diagnostic_node)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(
|
||||
"Invalid base for class created via `type()`",
|
||||
);
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Has type `{}`",
|
||||
base.display(db)
|
||||
));
|
||||
match class_base {
|
||||
ClassBase::Generic => {
|
||||
diagnostic.info(
|
||||
"Classes created via `type()` cannot be generic",
|
||||
);
|
||||
diagnostic.info(format_args!(
|
||||
"Consider using `class {name}(Generic[...]): ...` instead"
|
||||
));
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
diagnostic.info(
|
||||
"Classes created via `type()` cannot be TypedDicts",
|
||||
);
|
||||
diagnostic.info(format_args!(
|
||||
"Consider using `TypedDict(\"{name}\", {{}})` instead"
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
return ClassBase::unknown();
|
||||
}
|
||||
}
|
||||
return class_base;
|
||||
}
|
||||
ClassBase::Protocol => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&UNSUPPORTED_DYNAMIC_BASE, diagnostic_node)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(
|
||||
"Unsupported base for class created via `type()`",
|
||||
);
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Has type `{}`",
|
||||
base.display(db)
|
||||
));
|
||||
diagnostic.info(
|
||||
"Classes created via `type()` cannot be protocols",
|
||||
);
|
||||
diagnostic.info(format_args!(
|
||||
"Consider using `class {name}(Protocol): ...` instead"
|
||||
));
|
||||
}
|
||||
return ClassBase::unknown();
|
||||
}
|
||||
ClassBase::Class(class_type) => {
|
||||
// Check if base is @final (includes enums with members).
|
||||
if class_type.is_final(db) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&SUBCLASS_OF_FINAL_CLASS, diagnostic_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Class `{name}` cannot inherit from final class `{}`",
|
||||
class_type.name(db)
|
||||
));
|
||||
}
|
||||
return ClassBase::unknown();
|
||||
}
|
||||
|
||||
let diagnostic_node = bases_tuple_elts
|
||||
.and_then(|elts| elts.get(idx))
|
||||
.unwrap_or(bases_node);
|
||||
// Enum subclasses require the EnumMeta metaclass, which
|
||||
// expects special dict attributes that `type()` doesn't provide.
|
||||
if let Some((static_class, _)) =
|
||||
class_type.static_class_literal(db)
|
||||
{
|
||||
if is_enum_class_by_inheritance(db, static_class) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_BASE, diagnostic_node)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(
|
||||
"Invalid base for class created via `type()`",
|
||||
);
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Has type `{}`",
|
||||
base.display(db)
|
||||
));
|
||||
diagnostic.info(
|
||||
"Creating an enum class via `type()` is not supported",
|
||||
);
|
||||
diagnostic.info(format_args!(
|
||||
"Consider using `Enum(\"{name}\", [])` instead"
|
||||
));
|
||||
}
|
||||
return ClassBase::unknown();
|
||||
}
|
||||
}
|
||||
|
||||
// Collect disjoint bases for instance-layout-conflict checking.
|
||||
if let Some(disjoint_base) = class_type.nearest_disjoint_base(db)
|
||||
{
|
||||
disjoint_bases.insert(
|
||||
disjoint_base,
|
||||
idx,
|
||||
class_type.class_literal(db),
|
||||
);
|
||||
}
|
||||
|
||||
return class_base;
|
||||
}
|
||||
ClassBase::Dynamic(_) => return class_base,
|
||||
}
|
||||
}
|
||||
|
||||
// If that fails, check if the type is "type-like" (e.g., `type[Base]`).
|
||||
// For type-like bases we emit `unsupported-dynamic-base` and use
|
||||
@@ -9514,6 +9713,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
return Type::TypedDict(typed_dict);
|
||||
}
|
||||
|
||||
// Handle 3-argument `type(name, bases, dict)`.
|
||||
if let Type::ClassLiteral(class) = callable_type
|
||||
&& class.is_known(self.db(), KnownClass::Type)
|
||||
{
|
||||
return self.infer_builtins_type_call(call_expression, None);
|
||||
}
|
||||
|
||||
// We don't call `Type::try_call`, because we want to perform type inference on the
|
||||
// arguments after matching them to parameters, but before checking that the argument types
|
||||
// are assignable to any parameter annotations.
|
||||
@@ -9605,6 +9811,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
{
|
||||
self.called_functions.insert(function);
|
||||
}
|
||||
|
||||
// Warn when `final()` is called as a function (not a decorator).
|
||||
// Type checkers cannot interpret this usage and will not prevent subclassing.
|
||||
if function.is_known(self.db(), KnownFunction::Final) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INEFFECTIVE_FINAL, call_expression)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(
|
||||
"Type checkers will not prevent subclassing when `final()` is called as a function",
|
||||
);
|
||||
diagnostic.info("Use `@final` as a decorator on a class or method instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let class = match callable_type {
|
||||
@@ -13321,36 +13541,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let value_node = subscript.value.as_ref();
|
||||
|
||||
let inferred = match (value_ty, slice_ty) {
|
||||
(Type::Dynamic(_) | Type::Never, _) => Some(value_ty),
|
||||
|
||||
(Type::TypeAlias(alias), _) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
alias.value_type(self.db()),
|
||||
slice_ty,
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(_, Type::TypeAlias(alias)) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
value_ty,
|
||||
alias.value_type(self.db()),
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(Type::Union(union), _) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
|
||||
})),
|
||||
|
||||
(_, Type::Union(union)) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, value_ty, *element, expr_context)
|
||||
})),
|
||||
|
||||
// TODO: we can map over the intersection and fold the results back into an intersection,
|
||||
// but we need to make sure we avoid emitting a diagnostic if one positive element has a `__getitem__`
|
||||
// method but another does not. This means `infer_subscript_expression_types`
|
||||
// needs to return a `Result` rather than eagerly emitting diagnostics.
|
||||
(Type::Intersection(_), _) | (_, Type::Intersection(_)) => {
|
||||
Some(todo_type!("Subscript expressions with intersections"))
|
||||
(Type::Intersection(_), _) => {
|
||||
Some(todo_type!("Subscript expressions on intersections"))
|
||||
}
|
||||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
@@ -13430,16 +13630,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}),
|
||||
|
||||
(Type::LiteralString, Type::IntLiteral(_) | Type::BooleanLiteral(_)) => {
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::NominalInstance(nominal))
|
||||
if nominal.slice_literal(db).is_some() =>
|
||||
{
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
// Ex) Given `b"value"[1]`, return `97` (i.e., `ord(b"a")`)
|
||||
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
||||
i32::try_from(i64_int).ok().map(|i32_int| {
|
||||
@@ -13571,39 +13761,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Some(todo_type!("Inference of subscript on special form"))
|
||||
}
|
||||
|
||||
(
|
||||
Type::FunctionLiteral(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::BoundMethod(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::Callable(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeGuard(_)
|
||||
| Type::TypedDict(_)
|
||||
| Type::NewTypeInstance(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::TypeVar(_) // TODO: more complex logic required here!
|
||||
| Type::KnownBoundMethod(_),
|
||||
_,
|
||||
) => None,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(inferred) = inferred {
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::types::generics::{InferableTypeVars, walk_specialization};
|
||||
use crate::types::protocol_class::{ProtocolClass, walk_protocol_interface};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
@@ -179,7 +180,7 @@ impl<'db> Type<'db> {
|
||||
// recognise `str` as a subtype of `Container[str]`.
|
||||
structurally_satisfied.or(db, || {
|
||||
let Some(nominal_instance) = protocol.to_nominal_instance() else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
|
||||
// if `self` and `other` are *both* protocols, we also need to treat `self` as if it
|
||||
@@ -411,7 +412,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.0, other.0) {
|
||||
(_, NominalInstanceInner::Object) => ConstraintSet::from(true),
|
||||
(_, NominalInstanceInner::Object) => ConstraintSet::from_bool(db, true),
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
@@ -447,12 +448,12 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor),
|
||||
(NominalInstanceInner::Object, NominalInstanceInner::Object) => {
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
||||
class1.is_equivalent_to_impl(db, class2, inferable, visitor)
|
||||
}
|
||||
_ => ConstraintSet::from(false),
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,9 +466,9 @@ impl<'db> NominalInstanceType<'db> {
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.is_object() || other.is_object() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
let mut result = ConstraintSet::from(false);
|
||||
let mut result = ConstraintSet::from_bool(db, false);
|
||||
if let Some(self_spec) = self.tuple_spec(db) {
|
||||
if let Some(other_spec) = other.tuple_spec(db) {
|
||||
let compatible = self_spec.is_disjoint_from_impl(
|
||||
@@ -484,7 +485,8 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
|
||||
result.or(db, || {
|
||||
ConstraintSet::from(
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
!self
|
||||
.class(db)
|
||||
.could_coexist_in_mro_with(db, other.class(db)),
|
||||
@@ -713,8 +715,8 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
protocol,
|
||||
InferableTypeVars::None,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
.is_always_satisfied(db)
|
||||
}
|
||||
@@ -780,13 +782,13 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
_visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
let self_normalized = self.normalized(db);
|
||||
if self_normalized == Type::ProtocolInstance(other) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
ConstraintSet::from(self_normalized == other.normalized(db))
|
||||
ConstraintSet::from_bool(db, self_normalized == other.normalized(db))
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
||||
@@ -796,12 +798,12 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn is_disjoint_from_impl(
|
||||
self,
|
||||
_db: &'db dyn Db,
|
||||
db: &'db dyn Db,
|
||||
_other: Self,
|
||||
_inferable: InferableTypeVars<'_, 'db>,
|
||||
_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
|
||||
@@ -125,16 +125,16 @@ impl<'db> NewType<'db> {
|
||||
// base class, we don't have to keep looking.
|
||||
pub(crate) fn has_relation_to_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
|
||||
if self.is_equivalent_to_impl(db, other) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
for base in self.iter_bases(db) {
|
||||
if let NewTypeBase::NewType(base_newtype) = base {
|
||||
if base_newtype.is_equivalent_to_impl(db, other) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
|
||||
|
||||
@@ -292,76 +292,44 @@ impl<'db> ProtocolInterface<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
other.members(db).when_all(db, |other_member| {
|
||||
self.member_by_name(db, other_member.name)
|
||||
.when_some_and(|our_member| match (our_member.kind, other_member.kind) {
|
||||
// Method members are always immutable;
|
||||
// they can never be subtypes of/assignable to mutable attribute members.
|
||||
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
.when_some_and(db, |our_member| {
|
||||
match (our_member.kind, other_member.kind) {
|
||||
// Method members are always immutable;
|
||||
// they can never be subtypes of/assignable to mutable attribute members.
|
||||
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// A property member can only be a subtype of an attribute member
|
||||
// if the property is readable *and* writable.
|
||||
//
|
||||
// TODO: this should also consider the types of the members on both sides.
|
||||
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(
|
||||
property.getter(db).is_some() && property.setter(db).is_some(),
|
||||
)
|
||||
}
|
||||
// A property member can only be a subtype of an attribute member
|
||||
// if the property is readable *and* writable.
|
||||
//
|
||||
// TODO: this should also consider the types of the members on both sides.
|
||||
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
property.getter(db).is_some() && property.setter(db).is_some(),
|
||||
)
|
||||
}
|
||||
|
||||
// A `@property` member can never be a subtype of a method member, as it is not necessarily
|
||||
// accessible on the meta-type, whereas a method member must be.
|
||||
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
// A `@property` member can never be a subtype of a method member, as it is not necessarily
|
||||
// accessible on the meta-type, whereas a method member must be.
|
||||
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// But an attribute member *can* be a subtype of a method member,
|
||||
// providing it is marked `ClassVar`
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Method(other_type),
|
||||
) => ConstraintSet::from(
|
||||
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
|
||||
)
|
||||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
// But an attribute member *can* be a subtype of a method member,
|
||||
// providing it is marked `ClassVar`
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Method(other_type),
|
||||
) => ConstraintSet::from_bool(
|
||||
db,
|
||||
Type::Callable(protocol_bind_self(db, other_type, None)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
protocol_bind_self(db, other_method, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Other(other_type),
|
||||
) => our_type
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
|
||||
)
|
||||
.and(db, || {
|
||||
other_type.has_relation_to_impl(
|
||||
our_type.has_relation_to_impl(
|
||||
db,
|
||||
our_type,
|
||||
Type::Callable(protocol_bind_self(db, other_type, None)),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
@@ -369,14 +337,50 @@ impl<'db> ProtocolInterface<'db> {
|
||||
)
|
||||
}),
|
||||
|
||||
// TODO: finish assignability/subtyping between two `@property` members,
|
||||
// and between a `@property` member and a member of a different kind.
|
||||
(
|
||||
ProtocolMemberKind::Property(_)
|
||||
| ProtocolMemberKind::Method(_)
|
||||
| ProtocolMemberKind::Other(_),
|
||||
ProtocolMemberKind::Property(_),
|
||||
) => ConstraintSet::from(true),
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db, None).has_relation_to_impl(
|
||||
db,
|
||||
protocol_bind_self(db, other_method, None),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Other(other_type),
|
||||
) => our_type
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
other_type.has_relation_to_impl(
|
||||
db,
|
||||
our_type,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
// TODO: finish assignability/subtyping between two `@property` members,
|
||||
// and between a `@property` member and a member of a different kind.
|
||||
(
|
||||
ProtocolMemberKind::Property(_)
|
||||
| ProtocolMemberKind::Method(_)
|
||||
| ProtocolMemberKind::Other(_),
|
||||
ProtocolMemberKind::Property(_),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -713,7 +717,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
match &self.kind {
|
||||
// TODO: implement disjointness for property/method members as well as attribute members
|
||||
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(
|
||||
db,
|
||||
@@ -764,7 +768,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
)
|
||||
.place
|
||||
else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
attribute_type
|
||||
};
|
||||
@@ -783,7 +787,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
let fallback_other = other.literal_fallback_instance(db).unwrap_or(other);
|
||||
attribute_type
|
||||
.try_upcast_to_callable(db)
|
||||
.when_some_and(|callables| {
|
||||
.when_some_and(db, |callables| {
|
||||
callables
|
||||
.map(|callable| callable.apply_self(db, fallback_other))
|
||||
.has_relation_to_impl(
|
||||
@@ -797,13 +801,16 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
})
|
||||
}
|
||||
// TODO: consider the types of the attribute on `other` for property members
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||
other.member(db, self.name).place,
|
||||
Place::Defined(DefinedPlace {
|
||||
definedness: Definedness::AlwaysDefined,
|
||||
..
|
||||
})
|
||||
)),
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from_bool(
|
||||
db,
|
||||
matches!(
|
||||
other.member(db, self.name).place,
|
||||
Place::Defined(DefinedPlace {
|
||||
definedness: Definedness::AlwaysDefined,
|
||||
..
|
||||
})
|
||||
),
|
||||
),
|
||||
ProtocolMemberKind::Other(member_type) => {
|
||||
let Place::Defined(DefinedPlace {
|
||||
ty: attribute_type,
|
||||
@@ -811,7 +818,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
..
|
||||
}) = other.member(db, self.name).place
|
||||
else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
member_type
|
||||
.has_relation_to_impl(
|
||||
|
||||
@@ -310,8 +310,8 @@ impl<'db> Type<'db> {
|
||||
target,
|
||||
inferable,
|
||||
relation,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ impl<'db> Type<'db> {
|
||||
// Note that we could do a full equivalence check here, but that would be both expensive
|
||||
// and unnecessary. This early return is only an optimisation.
|
||||
if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
// Handle constraint implication first. If either `self` or `target` is a typevar, check
|
||||
@@ -359,22 +359,22 @@ impl<'db> Type<'db> {
|
||||
match (self, target) {
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object() => {
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => {
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
// It is a subtype of all other types.
|
||||
(Type::Never, _) => ConstraintSet::from(true),
|
||||
(Type::Never, _) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
// In some specific situations, `Any`/`Unknown`/`@Todo` can be simplified out of unions and intersections,
|
||||
// but this is not true for divergent types (and moving this case any lower down appears to cause
|
||||
// "too many cycle iterations" panics).
|
||||
(Type::Dynamic(DynamicType::Divergent(_)), _)
|
||||
| (_, Type::Dynamic(DynamicType::Divergent(_))) => {
|
||||
ConstraintSet::from(relation.is_assignability())
|
||||
ConstraintSet::from_bool(db, relation.is_assignability())
|
||||
}
|
||||
|
||||
(Type::TypeAlias(self_alias), _) => {
|
||||
@@ -409,7 +409,7 @@ impl<'db> Type<'db> {
|
||||
(Type::KnownInstance(KnownInstanceType::Field(field)), right)
|
||||
if relation.is_assignability() =>
|
||||
{
|
||||
field.default_type(db).when_none_or(|default_type| {
|
||||
field.default_type(db).when_none_or(db, |default_type| {
|
||||
default_type.has_relation_to_impl(
|
||||
db,
|
||||
right,
|
||||
@@ -434,31 +434,39 @@ impl<'db> Type<'db> {
|
||||
!matches!(dynamic, DynamicType::Divergent(_)),
|
||||
"DynamicType::Divergent should have been handled in an earlier branch"
|
||||
);
|
||||
ConstraintSet::from(match relation {
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
|
||||
true
|
||||
}
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
_ => false,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from_bool(
|
||||
db,
|
||||
match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
|
||||
TypeRelation::Redundancy => match target {
|
||||
TypeRelation::Redundancy => match self {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||
Type::Intersection(intersection) => {
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(Type::is_non_divergent_dynamic)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
|
||||
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
|
||||
TypeRelation::Redundancy => match self {
|
||||
Type::Dynamic(_) => true,
|
||||
Type::Intersection(intersection) => {
|
||||
// If a `Divergent` type is involved, it must not be eliminated.
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(Type::is_non_divergent_dynamic)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
||||
@@ -471,7 +479,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& union.elements(db).contains(&self) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
@@ -479,13 +487,13 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// Two identical typevars must always solve to the same type, so they are always
|
||||
@@ -497,7 +505,7 @@ impl<'db> Type<'db> {
|
||||
if !lhs_bound_typevar.is_inferable(db, inferable)
|
||||
&& lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance
|
||||
@@ -506,7 +514,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(target.to_instance(db))
|
||||
.when_some_and(|(this_instance, other_instance)| {
|
||||
.when_some_and(db, |(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -522,7 +530,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(target.to_instance(db))
|
||||
.when_some_and(|(this_instance, other_instance)| {
|
||||
.when_some_and(db, |(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -538,7 +546,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
.when_some_and(db, |(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
@@ -554,7 +562,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
.when_some_and(db, |(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
@@ -607,7 +615,7 @@ impl<'db> Type<'db> {
|
||||
&& !bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(|constraints| {
|
||||
.when_some_and(db, |constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
@@ -628,7 +636,7 @@ impl<'db> Type<'db> {
|
||||
bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(|constraints| {
|
||||
.when_some_and(db, |constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
@@ -648,11 +656,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: record the unification constraints
|
||||
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
(_, Type::Never) => ConstraintSet::from(false),
|
||||
(_, Type::Never) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(target_newtype)) => {
|
||||
self_newtype.has_relation_to_impl(db, target_newtype)
|
||||
@@ -711,7 +719,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
} else {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -815,7 +823,7 @@ impl<'db> Type<'db> {
|
||||
// bound. This is true even if the bound is a final class, since the typevar can still
|
||||
// be specialized to `Never`.)
|
||||
(_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
(_, Type::TypeVar(typevar))
|
||||
@@ -836,26 +844,29 @@ impl<'db> Type<'db> {
|
||||
{
|
||||
// TODO: record the unification constraints
|
||||
|
||||
typevar.typevar(db).upper_bound(db).when_none_or(|bound| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
bound,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
typevar
|
||||
.typevar(db)
|
||||
.upper_bound(db)
|
||||
.when_none_or(db, |bound| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
bound,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(_, Type::TypeVar(bound_typevar)) if bound_typevar.is_inferable(db, inferable) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
(Type::TypeVar(bound_typevar), _) => {
|
||||
// All inferable cases should have been handled above
|
||||
assert!(!bound_typevar.is_inferable(db, inferable));
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// All other `NewType` assignments fall back to the concrete base type.
|
||||
@@ -875,8 +886,12 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
|
||||
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
|
||||
(left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),
|
||||
(left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()),
|
||||
(left, Type::AlwaysFalsy) => {
|
||||
ConstraintSet::from_bool(db, left.bool(db).is_always_false())
|
||||
}
|
||||
(left, Type::AlwaysTruthy) => {
|
||||
ConstraintSet::from_bool(db, left.bool(db).is_always_true())
|
||||
}
|
||||
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
||||
target.when_equivalent_to(db, Type::object(), inferable)
|
||||
@@ -936,7 +951,7 @@ impl<'db> Type<'db> {
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::EnumLiteral(_),
|
||||
) => ConstraintSet::from(false),
|
||||
) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor
|
||||
.visit((self, target, relation), || {
|
||||
@@ -952,16 +967,17 @@ impl<'db> Type<'db> {
|
||||
|
||||
(_, Type::Callable(other_callable)) => {
|
||||
relation_visitor.visit((self, target, relation), || {
|
||||
self.try_upcast_to_callable(db).when_some_and(|callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
self.try_upcast_to_callable(db)
|
||||
.when_some_and(db, |callables| {
|
||||
callables.has_relation_to_impl(
|
||||
db,
|
||||
other_callable,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -999,7 +1015,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`.
|
||||
(Type::ProtocolInstance(_), _) => ConstraintSet::from(false),
|
||||
(Type::ProtocolInstance(_), _) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::TypedDict(self_typeddict), Type::TypedDict(other_typeddict)) => relation_visitor
|
||||
.visit((self, target, relation), || {
|
||||
@@ -1031,22 +1047,22 @@ impl<'db> Type<'db> {
|
||||
}),
|
||||
|
||||
// A non-`TypedDict` cannot subtype a `TypedDict`
|
||||
(_, Type::TypedDict(_)) => ConstraintSet::from(false),
|
||||
(_, Type::TypedDict(_)) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
// All `StringLiteral` types are a subtype of `LiteralString`.
|
||||
(Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from(true),
|
||||
(Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
// An instance is a subtype of an enum literal, if it is an instance of the enum class
|
||||
// and the enum has only one member.
|
||||
(Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => {
|
||||
if target_enum_literal.enum_class_instance(db) != self {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
ConstraintSet::from(is_single_member_enum(
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
target_enum_literal.enum_class(db),
|
||||
))
|
||||
is_single_member_enum(db, target_enum_literal.enum_class(db)),
|
||||
)
|
||||
}
|
||||
|
||||
// Except for the special `LiteralString` case above,
|
||||
@@ -1062,7 +1078,7 @@ impl<'db> Type<'db> {
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::FunctionLiteral(_),
|
||||
_,
|
||||
) => (self.literal_fallback_instance(db)).when_some_and(|instance| {
|
||||
) => (self.literal_fallback_instance(db)).when_some_and(db, |instance| {
|
||||
instance.has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
@@ -1107,7 +1123,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
|
||||
// TODO: Implement subtyping using an equivalent `Callable` type.
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// `TypeIs` is invariant.
|
||||
@@ -1170,7 +1186,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
(Type::Callable(_), _) => ConstraintSet::from(false),
|
||||
(Type::Callable(_), _) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => {
|
||||
self.when_equivalent_to(db, target, inferable)
|
||||
@@ -1187,7 +1203,7 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of))
|
||||
if subclass_of.is_type_var() =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
||||
@@ -1205,7 +1221,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||
.unwrap_or_else(|| ConstraintSet::from_bool(db, relation.is_assignability())),
|
||||
|
||||
// Similarly, `<class 'C'>` is assignable to `<class 'C[...]'>` (a generic-alias type)
|
||||
// if the default specialization of `C` is assignable to `C[...]`. This scenario occurs
|
||||
@@ -1247,7 +1263,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||
.unwrap_or_else(|| ConstraintSet::from_bool(db, relation.is_assignability())),
|
||||
|
||||
// This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`?
|
||||
(Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => {
|
||||
@@ -1298,7 +1314,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
)
|
||||
.or(db, || {
|
||||
ConstraintSet::from(relation.is_assignability()).and(db, || {
|
||||
ConstraintSet::from_bool(db, relation.is_assignability()).and(db, || {
|
||||
other.has_relation_to_impl(
|
||||
db,
|
||||
KnownClass::Type.to_instance(db),
|
||||
@@ -1403,7 +1419,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Other than the special cases enumerated above, nominal-instance types are never
|
||||
// subtypes of any other variants
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1430,7 +1446,7 @@ impl<'db> Type<'db> {
|
||||
other: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default())
|
||||
self.is_equivalent_to_impl(db, other, inferable, &default_equivalent_visitor(db))
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to_impl(
|
||||
@@ -1441,7 +1457,7 @@ impl<'db> Type<'db> {
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
@@ -1449,17 +1465,17 @@ impl<'db> Type<'db> {
|
||||
// which prevents `Divergent` from being eliminated during union reduction.
|
||||
(Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_)))
|
||||
| (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
(Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true),
|
||||
(Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
||||
match (first.subclass_of(), second.subclass_of()) {
|
||||
(first, second) if first == second => ConstraintSet::from(true),
|
||||
(first, second) if first == second => ConstraintSet::from_bool(db, true),
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
_ => ConstraintSet::from(false),
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1478,7 +1494,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(other_newtype)) => {
|
||||
ConstraintSet::from(self_newtype.is_equivalent_to_impl(db, other_newtype))
|
||||
ConstraintSet::from_bool(db, self_newtype.is_equivalent_to_impl(db, other_newtype))
|
||||
}
|
||||
|
||||
(Type::NominalInstance(first), Type::NominalInstance(second)) => {
|
||||
@@ -1511,16 +1527,16 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
ConstraintSet::from(n.is_object() && protocol.normalized(db) == nominal)
|
||||
ConstraintSet::from_bool(db, n.is_object() && protocol.normalized(db) == nominal)
|
||||
}
|
||||
// An instance of an enum class is equivalent to an enum literal of that class,
|
||||
// if that enum has only has one member.
|
||||
(Type::NominalInstance(instance), Type::EnumLiteral(literal))
|
||||
| (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => {
|
||||
if literal.enum_class_instance(db) != Type::NominalInstance(instance) {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db)))
|
||||
ConstraintSet::from_bool(db, is_single_member_enum(db, instance.class_literal(db)))
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
|
||||
@@ -1531,7 +1547,7 @@ impl<'db> Type<'db> {
|
||||
left.is_equivalent_to_impl(db, right, inferable, visitor)
|
||||
}),
|
||||
|
||||
_ => ConstraintSet::from(false),
|
||||
_ => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1565,8 +1581,8 @@ impl<'db> Type<'db> {
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
&IsDisjointVisitor::default(),
|
||||
&HasRelationToVisitor::default(),
|
||||
&default_disjoint_visitor(db),
|
||||
&default_relation_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1591,7 +1607,7 @@ impl<'db> Type<'db> {
|
||||
.member(db, member.name())
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.when_none_or(|attribute_type| {
|
||||
.when_none_or(db, |attribute_type| {
|
||||
member.has_disjoint_type_from(
|
||||
db,
|
||||
attribute_type,
|
||||
@@ -1604,9 +1620,9 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Type::Never, _) | (_, Type::Never) => ConstraintSet::from(true),
|
||||
(Type::Never, _) | (_, Type::Never) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from(false),
|
||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::TypeAlias(alias), _) => {
|
||||
let self_alias_ty = alias.value_type(db);
|
||||
@@ -1659,7 +1675,7 @@ impl<'db> Type<'db> {
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
.when_none_or(db, |(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -1674,7 +1690,7 @@ impl<'db> Type<'db> {
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
.when_none_or(db, |(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
@@ -1693,7 +1709,7 @@ impl<'db> Type<'db> {
|
||||
if !self_bound_typevar.is_inferable(db, inferable)
|
||||
&& self_bound_typevar.is_same_typevar_as(db, other_bound_typevar) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
(tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection))
|
||||
@@ -1701,7 +1717,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&tvar) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// An unbounded typevar is never disjoint from any other type, since it might be
|
||||
@@ -1712,7 +1728,7 @@ impl<'db> Type<'db> {
|
||||
if !bound_typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => ConstraintSet::from(false),
|
||||
None => ConstraintSet::from_bool(db, false),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound
|
||||
.is_disjoint_from_impl(
|
||||
db,
|
||||
@@ -1736,7 +1752,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false),
|
||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::Union(union), other) | (other, Type::Union(union)) => {
|
||||
union.elements(db).iter().when_all(db, |e| {
|
||||
@@ -1841,7 +1857,7 @@ impl<'db> Type<'db> {
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::SpecialForm(..)
|
||||
| Type::KnownInstance(..)),
|
||||
) => ConstraintSet::from(left != right),
|
||||
) => ConstraintSet::from_bool(db, left != right),
|
||||
|
||||
(
|
||||
Type::SubclassOf(_),
|
||||
@@ -1870,16 +1886,16 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::ModuleLiteral(..),
|
||||
Type::SubclassOf(_),
|
||||
) => ConstraintSet::from(true),
|
||||
) => ConstraintSet::from_bool(db, true),
|
||||
|
||||
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
||||
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
||||
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
||||
ConstraintSet::from(ty.bool(db).is_always_false())
|
||||
ConstraintSet::from_bool(db, ty.bool(db).is_always_false())
|
||||
}
|
||||
(Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => {
|
||||
// Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`.
|
||||
ConstraintSet::from(ty.bool(db).is_always_true())
|
||||
ConstraintSet::from_bool(db, ty.bool(db).is_always_true())
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor
|
||||
@@ -2011,7 +2027,7 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
),
|
||||
Place::Undefined => ConstraintSet::from(false),
|
||||
Place::Undefined => ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -2020,26 +2036,29 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), _) | (_, Type::SubclassOf(subclass_of_ty))
|
||||
if subclass_of_ty.is_type_var() =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(left_alias), Type::GenericAlias(right_alias)) => {
|
||||
ConstraintSet::from(left_alias.origin(db) != right_alias.origin(db)).or(db, || {
|
||||
left_alias.specialization(db).is_disjoint_from_impl(
|
||||
db,
|
||||
right_alias.specialization(db),
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
ConstraintSet::from_bool(db, left_alias.origin(db) != right_alias.origin(db)).or(
|
||||
db,
|
||||
|| {
|
||||
left_alias.specialization(db).is_disjoint_from_impl(
|
||||
db,
|
||||
right_alias.specialization(db),
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
(Type::ClassLiteral(class_literal), other @ Type::GenericAlias(_))
|
||||
| (other @ Type::GenericAlias(_), Type::ClassLiteral(class_literal)) => class_literal
|
||||
.default_specialization(db)
|
||||
.into_generic_alias()
|
||||
.when_none_or(|alias| {
|
||||
.when_none_or(db, |alias| {
|
||||
other.is_disjoint_from_impl(
|
||||
db,
|
||||
Type::GenericAlias(alias),
|
||||
@@ -2052,8 +2071,9 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
||||
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||
match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from_bool(db, false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from_bool(
|
||||
db,
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::NonGeneric(class_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
@@ -2063,8 +2083,9 @@ impl<'db> Type<'db> {
|
||||
(Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
|
||||
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||
match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from(
|
||||
SubclassOfInner::Dynamic(_) => ConstraintSet::from_bool(db, false),
|
||||
SubclassOfInner::Class(class_a) => ConstraintSet::from_bool(
|
||||
db,
|
||||
!class_a.could_exist_in_mro_of(db, ClassType::Generic(alias_b)),
|
||||
),
|
||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||
@@ -2102,12 +2123,12 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
||||
ConstraintSet::from(!special_form.is_instance_of(db, instance.class(db)))
|
||||
ConstraintSet::from_bool(db, !special_form.is_instance_of(db, instance.class(db)))
|
||||
}
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
|
||||
ConstraintSet::from(!known_instance.is_instance_of(db, instance.class(db)))
|
||||
ConstraintSet::from_bool(db, !known_instance.is_instance_of(db, instance.class(db)))
|
||||
}
|
||||
|
||||
(
|
||||
@@ -2127,7 +2148,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_) | Type::TypeGuard(_), _)
|
||||
| (_, Type::BooleanLiteral(..) | Type::TypeIs(_) | Type::TypeGuard(_)) => {
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), Type::NominalInstance(instance))
|
||||
@@ -2139,10 +2160,12 @@ impl<'db> Type<'db> {
|
||||
.negate(db)
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true),
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::StringLiteral(..), Type::LiteralString)
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from(false),
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from_bool(db, false),
|
||||
|
||||
(Type::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
|
||||
@@ -2153,8 +2176,10 @@ impl<'db> Type<'db> {
|
||||
.negate(db)
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false),
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true),
|
||||
(Type::LiteralString, Type::LiteralString) => ConstraintSet::from_bool(db, false),
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::BytesLiteral(..), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
|
||||
@@ -2179,7 +2204,9 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.negate(db)
|
||||
}
|
||||
(Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true),
|
||||
(Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
// A class-literal type `X` is always disjoint from an instance type `Y`,
|
||||
// unless the type expressing "all instances of `Z`" is a subtype of of `Y`,
|
||||
@@ -2250,7 +2277,7 @@ impl<'db> Type<'db> {
|
||||
// No two callable types are ever disjoint because
|
||||
// `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static
|
||||
// callable types.
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
(Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_))
|
||||
@@ -2258,7 +2285,7 @@ impl<'db> Type<'db> {
|
||||
// A callable type is disjoint from other literal types. For example,
|
||||
// `Type::StringLiteral` must be an instance of exactly `str`, not a subclass
|
||||
// of `str`, and `str` is not callable. The same applies to other literal types.
|
||||
ConstraintSet::from(true)
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::Callable(_), Type::SpecialForm(special_form))
|
||||
@@ -2267,7 +2294,7 @@ impl<'db> Type<'db> {
|
||||
// that are callable (like TypedDict and collection constructors).
|
||||
// Most special forms are type constructors/annotations (like `typing.Literal`,
|
||||
// `typing.Union`, etc.) that are subscripted, not called.
|
||||
ConstraintSet::from(!special_form.is_callable())
|
||||
ConstraintSet::from_bool(db, !special_form.is_callable())
|
||||
}
|
||||
|
||||
(
|
||||
@@ -2285,7 +2312,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.when_none_or(|dunder_call| {
|
||||
.when_none_or(db, |dunder_call| {
|
||||
dunder_call
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
@@ -2307,7 +2334,7 @@ impl<'db> Type<'db> {
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
) => {
|
||||
// TODO: Implement disjointness for general callable type with other types
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
|
||||
(Type::ModuleLiteral(..), other @ Type::NominalInstance(..))
|
||||
@@ -2369,7 +2396,9 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(_), _) | (_, Type::GenericAlias(_)) => ConstraintSet::from(true),
|
||||
(Type::GenericAlias(_), _) | (_, Type::GenericAlias(_)) => {
|
||||
ConstraintSet::from_bool(db, true)
|
||||
}
|
||||
|
||||
(Type::TypedDict(self_typeddict), Type::TypedDict(other_typeddict)) => {
|
||||
disjointness_visitor.visit((self, other), || {
|
||||
@@ -2416,10 +2445,8 @@ fn is_redundant_with_cycle_initial<'db>(
|
||||
pub(crate) type HasRelationToVisitor<'db> =
|
||||
CycleDetector<TypeRelation<'db>, (Type<'db>, Type<'db>, TypeRelation<'db>), ConstraintSet<'db>>;
|
||||
|
||||
impl Default for HasRelationToVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
HasRelationToVisitor::new(ConstraintSet::from(true))
|
||||
}
|
||||
pub(crate) fn default_relation_visitor(db: &dyn Db) -> HasRelationToVisitor<'_> {
|
||||
HasRelationToVisitor::new(ConstraintSet::from_bool(db, true))
|
||||
}
|
||||
|
||||
/// A [`PairVisitor`] that is used in `is_disjoint_from` methods.
|
||||
@@ -2428,10 +2455,8 @@ pub(crate) type IsDisjointVisitor<'db> = PairVisitor<'db, IsDisjoint, Constraint
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IsDisjoint;
|
||||
|
||||
impl Default for IsDisjointVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
IsDisjointVisitor::new(ConstraintSet::from(false))
|
||||
}
|
||||
pub(crate) fn default_disjoint_visitor(db: &dyn Db) -> IsDisjointVisitor<'_> {
|
||||
IsDisjointVisitor::new(ConstraintSet::from_bool(db, false))
|
||||
}
|
||||
|
||||
/// A [`PairVisitor`] that is used in `is_equivalent` methods.
|
||||
@@ -2440,8 +2465,6 @@ pub(crate) type IsEquivalentVisitor<'db> = PairVisitor<'db, IsEquivalent, Constr
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IsEquivalent;
|
||||
|
||||
impl Default for IsEquivalentVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
IsEquivalentVisitor::new(ConstraintSet::from(true))
|
||||
}
|
||||
pub(crate) fn default_equivalent_visitor(db: &dyn Db) -> IsEquivalentVisitor<'_> {
|
||||
IsEquivalentVisitor::new(ConstraintSet::from_bool(db, true))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_con
|
||||
use crate::types::infer::{infer_deferred_types, infer_scope_types};
|
||||
use crate::types::relation::{
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, TypeRelation,
|
||||
default_disjoint_visitor, default_relation_visitor,
|
||||
};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableType, CallableTypeKind,
|
||||
@@ -306,8 +307,8 @@ impl<'db> CallableSignature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -362,8 +363,8 @@ impl<'db> CallableSignature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::ConstraintSetAssignability,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -549,7 +550,7 @@ impl<'db> CallableSignature<'db> {
|
||||
}
|
||||
(_, _) => {
|
||||
if self == other {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
self.is_subtype_of_impl(db, other, inferable)
|
||||
.and(db, || other.is_subtype_of_impl(db, self, inferable))
|
||||
@@ -1005,14 +1006,14 @@ impl<'db> Signature<'db> {
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
|
||||
if self.parameters.is_gradual() != other.parameters.is_gradual() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
if self.parameters.len() != other.parameters.len() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
let mut check_types = |self_type: Type<'db>, other_type: Type<'db>| {
|
||||
@@ -1069,7 +1070,7 @@ impl<'db> Signature<'db> {
|
||||
|
||||
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
|
||||
|
||||
_ => return ConstraintSet::from(false),
|
||||
_ => return ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
|
||||
if !check_types(
|
||||
@@ -1132,8 +1133,8 @@ impl<'db> Signature<'db> {
|
||||
other,
|
||||
inferable,
|
||||
TypeRelation::ConstraintSetAssignability,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
&default_relation_visitor(db),
|
||||
&default_disjoint_visitor(db),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1251,7 +1252,7 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
|
||||
let mut check_types = |type1: Type<'db>, type2: Type<'db>| {
|
||||
match (type1, type2) {
|
||||
@@ -1308,15 +1309,15 @@ impl<'db> Signature<'db> {
|
||||
.keyword_variadic()
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_object())
|
||||
{
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
// The top signature is supertype of (and assignable from) all other signatures. It is a
|
||||
// subtype of no signature except itself, and assignable only to the gradual signature.
|
||||
if other.parameters.is_top() {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
} else if self.parameters.is_top() && !other.parameters.is_gradual() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
|
||||
@@ -1324,7 +1325,8 @@ impl<'db> Signature<'db> {
|
||||
if self.parameters.is_gradual() || other.parameters.is_gradual() {
|
||||
result.intersect(
|
||||
db,
|
||||
ConstraintSet::from(
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
relation.is_assignability() || relation.is_constraint_set_assignability(),
|
||||
),
|
||||
);
|
||||
@@ -1426,7 +1428,7 @@ impl<'db> Signature<'db> {
|
||||
// `other`, then the non-variadic parameters in `self` must have a default
|
||||
// value.
|
||||
if default_type.is_none() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
}
|
||||
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
|
||||
@@ -1438,7 +1440,7 @@ impl<'db> Signature<'db> {
|
||||
EitherOrBoth::Right(_) => {
|
||||
// If there are more parameters in `other` than in `self`, then `self` is not a
|
||||
// subtype of `other`.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
EitherOrBoth::Both(self_parameter, other_parameter) => {
|
||||
@@ -1458,7 +1460,7 @@ impl<'db> Signature<'db> {
|
||||
},
|
||||
) => {
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1479,11 +1481,11 @@ impl<'db> Signature<'db> {
|
||||
},
|
||||
) => {
|
||||
if self_name != other_name {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
// The following checks are the same as positional-only parameters.
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1568,7 +1570,7 @@ impl<'db> Signature<'db> {
|
||||
break;
|
||||
}
|
||||
|
||||
_ => return ConstraintSet::from(false),
|
||||
_ => return ConstraintSet::from_bool(db, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1601,7 +1603,7 @@ impl<'db> Signature<'db> {
|
||||
// previous loop. They cannot be matched against any parameter in `other` which
|
||||
// only contains keyword-only and keyword-variadic parameters so the subtype
|
||||
// relation is invalid.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
ParameterKind::Variadic { .. } => {}
|
||||
}
|
||||
@@ -1628,7 +1630,7 @@ impl<'db> Signature<'db> {
|
||||
..
|
||||
} => {
|
||||
if self_default.is_none() && other_default.is_some() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
if !check_types(
|
||||
other_parameter.annotated_type(),
|
||||
@@ -1649,14 +1651,14 @@ impl<'db> Signature<'db> {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
}
|
||||
ParameterKind::KeywordVariadic { .. } => {
|
||||
let Some(self_keyword_variadic_type) = self_keyword_variadic else {
|
||||
// For a `self <: other` relationship, if `other` has a keyword variadic
|
||||
// parameter, `self` must also have a keyword variadic parameter.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
|
||||
return result;
|
||||
@@ -1664,7 +1666,7 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
_ => {
|
||||
// This can only occur in case of a syntax error.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1673,7 +1675,7 @@ impl<'db> Signature<'db> {
|
||||
// optional otherwise the subtype relation is invalid.
|
||||
for (_, self_parameter) in self_keywords {
|
||||
if self_parameter.default_type().is_none() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,13 +224,16 @@ impl<'db> SubclassOfType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from(!relation.is_subtyping())
|
||||
ConstraintSet::from_bool(db, !relation.is_subtyping())
|
||||
}
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
||||
ConstraintSet::from(other_class.is_object(db) || relation.is_assignability())
|
||||
ConstraintSet::from_bool(
|
||||
db,
|
||||
other_class.is_object(db) || relation.is_assignability(),
|
||||
)
|
||||
}
|
||||
(SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from(relation.is_assignability())
|
||||
ConstraintSet::from_bool(db, relation.is_assignability())
|
||||
}
|
||||
|
||||
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
|
||||
@@ -264,10 +267,10 @@ impl<'db> SubclassOfType<'db> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||
ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class))
|
||||
ConstraintSet::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class))
|
||||
}
|
||||
(SubclassOfInner::TypeVar(_), _) | (_, SubclassOfInner::TypeVar(_)) => {
|
||||
unreachable!()
|
||||
|
||||
@@ -516,7 +516,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
) -> ConstraintSet<'db> {
|
||||
match other {
|
||||
Tuple::Fixed(other) => {
|
||||
ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
|
||||
ConstraintSet::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||
(self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| {
|
||||
self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -533,11 +533,11 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
Tuple::Variable(other) => {
|
||||
// This tuple must have enough elements to match up with the other tuple's prefix
|
||||
// and suffix, and each of those elements must pairwise satisfy the relation.
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut self_iter = self.0.iter();
|
||||
for other_ty in other.prefix_elements() {
|
||||
let Some(self_ty) = self_iter.next() else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -556,7 +556,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
}
|
||||
for other_ty in other.iter_suffix_elements().rev() {
|
||||
let Some(self_ty) = self_iter.next_back() else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -599,7 +599,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
|
||||
ConstraintSet::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||
(self.0.iter())
|
||||
.zip(&other.0)
|
||||
.when_all(db, |(self_ty, other_ty)| {
|
||||
@@ -1031,17 +1031,17 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// possible lengths. This means that `tuple[Any, ...]` can match any tuple of any
|
||||
// length.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
// In addition, the other tuple must have enough elements to match up with this
|
||||
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
||||
// relation.
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let mut other_iter = other.iter_all_elements();
|
||||
for self_ty in self.prenormalized_prefix_elements(db, None) {
|
||||
let Some(other_ty) = other_iter.next() else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -1061,7 +1061,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
|
||||
for self_ty in suffix.iter().rev() {
|
||||
let Some(other_ty) = other_iter.next_back() else {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
@@ -1097,7 +1097,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
||||
// Any remaining parts must satisfy the relation with the other tuple's
|
||||
// variable-length part.
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut result = ConstraintSet::from_bool(db, true);
|
||||
let pairwise = self
|
||||
.prenormalized_prefix_elements(db, self_prenormalize_variable)
|
||||
.zip_longest(
|
||||
@@ -1127,7 +1127,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// that can materialize to provide it (for assignability only),
|
||||
// as in `tuple[Any, ...]` matching `tuple[int, int]`.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
self.variable().has_relation_to_impl(
|
||||
db,
|
||||
@@ -1181,7 +1181,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
// that can materialize to provide it (for assignability only),
|
||||
// as in `tuple[Any, ...]` matching `tuple[int, int]`.
|
||||
if !relation.is_assignability() || !self.variable().is_dynamic() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
self.variable().has_relation_to_impl(
|
||||
db,
|
||||
@@ -1233,7 +1233,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1245,7 +1245,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1513,7 +1513,7 @@ impl<'db> Tuple<Type<'db>> {
|
||||
self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor)
|
||||
}
|
||||
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1530,10 +1530,10 @@ impl<'db> Tuple<Type<'db>> {
|
||||
let (self_min, self_max) = self.len().size_hint();
|
||||
let (other_min, other_max) = other.len().size_hint();
|
||||
if self_max.is_some_and(|max| max < other_min) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
if other_max.is_some_and(|max| max < self_min) {
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
// If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
|
||||
|
||||
@@ -151,24 +151,24 @@ impl<'db> TypedDictType<'db> {
|
||||
&& let Some(target_defining_class) = target.defining_class()
|
||||
&& defining_class.is_subclass_of(db, target_defining_class)
|
||||
{
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
|
||||
let self_items = self.items(db);
|
||||
let target_items = target.items(db);
|
||||
// Many rules violations short-circuit with "never", but asking whether one field is
|
||||
// [relation] to/of another can produce more complicated constraints, and we collect those.
|
||||
let mut constraints = ConstraintSet::from(true);
|
||||
let mut constraints = ConstraintSet::from_bool(db, true);
|
||||
for (target_item_name, target_item_field) in target_items {
|
||||
let field_constraints = if target_item_field.is_required() {
|
||||
// required target fields
|
||||
let Some(self_item_field) = self_items.get(target_item_name) else {
|
||||
// Self is missing a required field.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
};
|
||||
if !self_item_field.is_required() {
|
||||
// A required field is not required in self.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
if target_item_field.is_read_only() {
|
||||
// For `ReadOnly[]` fields in the target, the corresponding fields in
|
||||
@@ -186,7 +186,7 @@ impl<'db> TypedDictType<'db> {
|
||||
} else {
|
||||
if self_item_field.is_read_only() {
|
||||
// A read-only field can't be assigned to a mutable target.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
// For mutable fields in the target, the relation needs to apply both
|
||||
// ways, or else mutating the target could violate the structural
|
||||
@@ -252,12 +252,12 @@ impl<'db> TypedDictType<'db> {
|
||||
if let Some(self_item_field) = self_items.get(target_item_name) {
|
||||
if self_item_field.is_read_only() {
|
||||
// A read-only field can't be assigned to a mutable target.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
if self_item_field.is_required() {
|
||||
// A required field can't be assigned to a not-required, mutable field
|
||||
// in the target, because `del` is allowed on the target field.
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
|
||||
// As above, for mutable fields in the target, the relation needs
|
||||
@@ -289,7 +289,7 @@ impl<'db> TypedDictType<'db> {
|
||||
// interaction between two structural assignability rules prevents
|
||||
// unsoundness" in `typed_dict.md`.
|
||||
// TODO: `closed` and `extra_items` support will go here.
|
||||
ConstraintSet::from(false)
|
||||
ConstraintSet::from_bool(db, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -303,14 +303,14 @@ impl<'db> TypedDictType<'db> {
|
||||
|
||||
pub fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
|
||||
match self {
|
||||
TypedDictType::Class(defining_class) => Some(defining_class.definition(db)),
|
||||
TypedDictType::Class(defining_class) => defining_class.definition(db),
|
||||
TypedDictType::Synthesized(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
|
||||
match self {
|
||||
TypedDictType::Class(defining_class) => Some(defining_class.type_definition(db)),
|
||||
TypedDictType::Class(defining_class) => defining_class.type_definition(db),
|
||||
TypedDictType::Synthesized(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -341,13 +341,13 @@ impl<'db> TypedDictType<'db> {
|
||||
// sorted order instead of paying for a lookup for each field, as long as their lengths are
|
||||
// the same.
|
||||
if self.items(db).len() != other.items(db).len() {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
self.items(db).iter().zip(other.items(db)).when_all(
|
||||
db,
|
||||
|((name, field), (other_name, other_field))| {
|
||||
if name != other_name || field.flags != other_field.flags {
|
||||
return ConstraintSet::from(false);
|
||||
return ConstraintSet::from_bool(db, false);
|
||||
}
|
||||
field.declared_ty.is_equivalent_to_impl(
|
||||
db,
|
||||
@@ -435,7 +435,7 @@ impl<'db> TypedDictType<'db> {
|
||||
{
|
||||
// One side demands a `Required` source field, while the other side demands a
|
||||
// `NotRequired` one. They must be disjoint.
|
||||
return ConstraintSet::from(true);
|
||||
return ConstraintSet::from_bool(db, true);
|
||||
}
|
||||
}
|
||||
if !self_field.is_read_only() && !other_field.is_read_only() {
|
||||
|
||||
10
ty.schema.json
generated
10
ty.schema.json
generated
@@ -520,6 +520,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ineffective-final": {
|
||||
"title": "detects calls to `final()` that type checkers cannot interpret",
|
||||
"description": "## What it does\nChecks for calls to `final()` that type checkers cannot interpret.\n\n## Why is this bad?\nThe `final()` function is designed to be used as a decorator. When called directly\nas a function (e.g., `final(type(...))`), type checkers will not understand the\napplication of `final` and will not prevent subclassing.\n\n## Example\n\n```python\nfrom typing import final\n\n# Incorrect: type checkers will not prevent subclassing\nMyClass = final(type(\"MyClass\", (), {}))\n\n# Correct: use `final` as a decorator\n@final\nclass MyClass: ...\n```",
|
||||
"default": "warn",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
}
|
||||
]
|
||||
},
|
||||
"instance-layout-conflict": {
|
||||
"title": "detects class definitions that raise `TypeError` due to instance layout conflict",
|
||||
"description": "## What it does\nChecks for classes definitions which will fail at runtime due to\n\"instance memory layout conflicts\".\n\nThis error is usually caused by attempting to combine multiple classes\nthat define non-empty `__slots__` in a class's [Method Resolution Order]\n(MRO), or by attempting to combine multiple builtin classes in a class's\nMRO.\n\n## Why is this bad?\nInheriting from bases with conflicting instance memory layouts\nwill lead to a `TypeError` at runtime.\n\nAn instance memory layout conflict occurs when CPython cannot determine\nthe memory layout instances of a class should have, because the instance\nmemory layout of one of its bases conflicts with the instance memory layout\nof one or more of its other bases.\n\nFor example, if a Python class defines non-empty `__slots__`, this will\nimpact the memory layout of instances of that class. Multiple inheritance\nfrom more than one different class defining non-empty `__slots__` is not\nallowed:\n\n```python\nclass A:\n __slots__ = (\"a\", \"b\")\n\nclass B:\n __slots__ = (\"a\", \"b\") # Even if the values are the same\n\n# TypeError: multiple bases have instance lay-out conflict\nclass C(A, B): ...\n```\n\nAn instance layout conflict can also be caused by attempting to use\nmultiple inheritance with two builtin classes, due to the way that these\nclasses are implemented in a CPython C extension:\n\n```python\nclass A(int, float): ... # TypeError: multiple bases have instance lay-out conflict\n```\n\nNote that pure-Python classes with no `__slots__`, or pure-Python classes\nwith empty `__slots__`, are always compatible:\n\n```python\nclass A: ...\nclass B:\n __slots__ = ()\nclass C:\n __slots__ = (\"a\", \"b\")\n\n# fine\nclass D(A, B, C): ...\n```\n\n## Known problems\nClasses that have \"dynamic\" definitions of `__slots__` (definitions do not consist\nof string literals, or tuples of string literals) are not currently considered disjoint\nbases by ty.\n\nAdditionally, this check is not exhaustive: many C extensions (including several in\nthe standard library) define classes that use extended memory layouts and thus cannot\ncoexist in a single MRO. Since it is currently not possible to represent this fact in\nstub files, having a full knowledge of these classes is also impossible. When it comes\nto classes that do not define `__slots__` at the Python level, therefore, ty, currently\nonly hard-codes a number of cases where it knows that a class will produce instances with\nan atypical memory layout.\n\n## Further reading\n- [CPython documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)\n- [CPython documentation: Method Resolution Order](https://docs.python.org/3/glossary.html#term-method-resolution-order)\n\n[Method Resolution Order]: https://docs.python.org/3/glossary.html#term-method-resolution-order",
|
||||
|
||||
Reference in New Issue
Block a user