Compare commits
10 Commits
ibraheem/c
...
alex/subsc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68bdf9fa88 | ||
|
|
b5814b91c1 | ||
|
|
ea46426157 | ||
|
|
ddd2fc7a90 | ||
|
|
4ebf10cf1b | ||
|
|
9a676bbeb7 | ||
|
|
d9028a098b | ||
|
|
56077ee9a9 | ||
|
|
20c01d2553 | ||
|
|
c98ea1bc24 |
@@ -106,6 +106,16 @@ impl Violation for PytestCompositeAssertion {
|
||||
/// assert exc_info.value.args
|
||||
/// ```
|
||||
///
|
||||
/// Or, for pytest 8.4.0 and later:
|
||||
/// ```python
|
||||
/// import pytest
|
||||
///
|
||||
///
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(ZeroDivisionError, check=lambda e: e.args):
|
||||
/// 1 / 0
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -140,7 +140,7 @@ pub(crate) fn add_required_imports(
|
||||
source_type: PySourceType,
|
||||
context: &LintContext,
|
||||
) {
|
||||
for required_import in &settings.isort.required_imports {
|
||||
for required_import in settings.isort.required_imports.iter().rev() {
|
||||
add_required_import(
|
||||
required_import,
|
||||
parsed,
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
I002 [*] Missing required import: `from __future__ import annotations`
|
||||
--> docstring.py:1:1
|
||||
help: Insert required import: `from __future__ import annotations`
|
||||
1 | """Hello, world!"""
|
||||
2 + from __future__ import annotations
|
||||
3 |
|
||||
4 | x = 1
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import generator_stop`
|
||||
--> docstring.py:1:1
|
||||
help: Insert required import: `from __future__ import generator_stop`
|
||||
@@ -16,3 +8,11 @@ help: Insert required import: `from __future__ import generator_stop`
|
||||
2 + from __future__ import generator_stop
|
||||
3 |
|
||||
4 | x = 1
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import annotations`
|
||||
--> docstring.py:1:1
|
||||
help: Insert required import: `from __future__ import annotations`
|
||||
1 | """Hello, world!"""
|
||||
2 + from __future__ import annotations
|
||||
3 |
|
||||
4 | x = 1
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
I002 [*] Missing required import: `from __future__ import annotations`
|
||||
--> multiple_strings.py:1:1
|
||||
help: Insert required import: `from __future__ import annotations`
|
||||
1 | """This is a docstring."""
|
||||
2 + from __future__ import annotations
|
||||
3 | "This is not a docstring."
|
||||
4 | "This is also not a docstring."
|
||||
5 |
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import generator_stop`
|
||||
--> multiple_strings.py:1:1
|
||||
help: Insert required import: `from __future__ import generator_stop`
|
||||
@@ -17,4 +8,13 @@ help: Insert required import: `from __future__ import generator_stop`
|
||||
2 + from __future__ import generator_stop
|
||||
3 | "This is not a docstring."
|
||||
4 | "This is also not a docstring."
|
||||
5 |
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import annotations`
|
||||
--> multiple_strings.py:1:1
|
||||
help: Insert required import: `from __future__ import annotations`
|
||||
1 | """This is a docstring."""
|
||||
2 + from __future__ import annotations
|
||||
3 | "This is not a docstring."
|
||||
4 | "This is also not a docstring."
|
||||
5 |
|
||||
|
||||
@@ -398,17 +398,17 @@ mod tests {
|
||||
1 + from pipes import Template
|
||||
2 + from shlex import quote
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import generator_stop`
|
||||
--> <filename>:1:1
|
||||
help: Insert required import: `from __future__ import generator_stop`
|
||||
1 + from __future__ import generator_stop
|
||||
2 | from pipes import quote, Template
|
||||
|
||||
I002 [*] Missing required import: `from collections import Sequence`
|
||||
--> <filename>:1:1
|
||||
help: Insert required import: `from collections import Sequence`
|
||||
1 + from collections import Sequence
|
||||
2 | from pipes import quote, Template
|
||||
|
||||
I002 [*] Missing required import: `from __future__ import generator_stop`
|
||||
--> <filename>:1:1
|
||||
help: Insert required import: `from __future__ import generator_stop`
|
||||
1 + from __future__ import generator_stop
|
||||
2 | from pipes import quote, Template
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
207
crates/ty/docs/rules.md
generated
207
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#L541" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L140" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.7">0.0.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L158" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ def f(x: object):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L209" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ f(int) # error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L235" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ a = 1
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L286" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ class B(A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ type B = A
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L356" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L357" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -346,7 +346,7 @@ false positives it can produce.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L377" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L378" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L398" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L624" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L648" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -585,7 +585,7 @@ t[3] # IndexError: tuple index out of range
|
||||
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#L430" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -674,7 +674,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -701,7 +701,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L742" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L743" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -729,7 +729,7 @@ a: int = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2079" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2122" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -763,7 +763,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L764" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -799,7 +799,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L794" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L795" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -823,7 +823,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L879" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L880" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -850,7 +850,7 @@ with 1:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L900" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -879,7 +879,7 @@ a: str
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L923" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -923,7 +923,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.28">0.0.1-alpha.28</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1792" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -965,7 +965,7 @@ class D(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.35">0.0.1-alpha.35</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2330" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2373" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1009,7 +1009,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1041,6 +1041,55 @@ class D(Generic[U, T]): ...
|
||||
|
||||
- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)
|
||||
|
||||
## `invalid-generic-enum`
|
||||
|
||||
<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.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>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for enum classes that are also generic.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Enum classes cannot be generic. Python does not support generic enums:
|
||||
attempting to create one will either result in an immediate `TypeError`
|
||||
at runtime, or will create a class that cannot be specialized in the way
|
||||
that a normal generic class can.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
from enum import Enum
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: enum class cannot be generic (class creation fails with `TypeError`)
|
||||
class E[T](Enum):
|
||||
A = 1
|
||||
|
||||
# error: enum class cannot be generic (class creation fails with `TypeError`)
|
||||
class F(Enum, Generic[T]):
|
||||
A = 1
|
||||
|
||||
# error: enum class cannot be generic -- the class creation does not immediately fail...
|
||||
class G(Generic[T], Enum):
|
||||
A = 1
|
||||
|
||||
# ...but this raises `KeyError`:
|
||||
x: G[int]
|
||||
```
|
||||
|
||||
**References**
|
||||
|
||||
- [Python documentation: Enum](https://docs.python.org/3/library/enum.html)
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
|
||||
<small>
|
||||
@@ -1077,7 +1126,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L669" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L670" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1116,7 +1165,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L990" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1033" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1151,7 +1200,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1130" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1185,7 +1234,7 @@ class B(metaclass=f): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2232" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2275" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1292,7 +1341,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L576" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L577" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1346,7 +1395,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1376,7 +1425,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1114" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1426,7 +1475,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1452,7 +1501,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1018" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1483,7 +1532,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L512" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1517,7 +1566,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1615,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L723" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L724" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1591,7 +1640,7 @@ def func() -> int:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1319" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1687,7 +1736,7 @@ class C: ...
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.10">0.0.10</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-total-ordering" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2368" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2411" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1733,7 +1782,7 @@ class MyClass:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1042" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1760,7 +1809,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1508" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1551" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1807,7 +1856,7 @@ Bar[int] # error: too few arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1315" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1358" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1837,7 +1886,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1339" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1867,7 +1916,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1391" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1901,7 +1950,7 @@ f(10) # Error
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1363" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1406" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1935,7 +1984,7 @@ class C:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1419" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1462" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1970,7 +2019,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.9">0.0.9</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-typed-dict-statement" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2207" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2250" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2001,7 +2050,7 @@ class Foo(TypedDict):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1448" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2026,7 +2075,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2180" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2223" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2059,7 +2108,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1467" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1510" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2088,7 +2137,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1549" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1592" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2114,7 +2163,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1490" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1533" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2138,7 +2187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.29">0.0.1-alpha.29</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1722" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2171,7 +2220,7 @@ class B(A):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1600" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2198,7 +2247,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1933" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2225,7 +2274,7 @@ f(x=1) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1621" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2253,7 +2302,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L184" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2285,7 +2334,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1686" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2322,7 +2371,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1673" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2386,7 +2435,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2107" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2150" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2413,7 +2462,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2055" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2098" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2443,7 +2492,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1699" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1742" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2472,7 +2521,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1867" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1910" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2506,7 +2555,7 @@ class F(NamedTuple):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1807" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1850" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2533,7 +2582,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1785" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1828" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2561,7 +2610,7 @@ def _(x: int):
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1828" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1871" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2607,7 +2656,7 @@ class A:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1894" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1937" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2631,7 +2680,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1912" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1955" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2658,7 +2707,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1954" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2686,7 +2735,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2128" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2171" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2744,7 +2793,7 @@ def g():
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2019" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2769,7 +2818,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1995" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2038" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2794,7 +2843,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2833,7 +2882,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1612" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2870,7 +2919,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-dynamic-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2911,7 +2960,7 @@ def factory(base: type[Base]) -> type:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2014" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2057" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2975,7 +3024,7 @@ to `false` to prevent this rule from reporting unused `type: ignore` comments.
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -3038,7 +3087,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2036" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2079" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,12 @@ def f[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
|
||||
l5: int | list[int] = f(1, True)
|
||||
|
||||
a: list[int] = [1, 2, *(3, 4, 5)]
|
||||
reveal_type(a) # revealed: list[int]
|
||||
|
||||
b: list[list[int]] = [[1], [2], *([3], [4])]
|
||||
reveal_type(b) # revealed: list[list[int]]
|
||||
```
|
||||
|
||||
`typed_dict.py`:
|
||||
|
||||
@@ -834,7 +834,7 @@ class Base: ...
|
||||
bases_tuple = (Base,)
|
||||
Cls1 = type("Cls1", (*bases_tuple,), {})
|
||||
reveal_type(Cls1) # revealed: <class 'Cls1'>
|
||||
reveal_mro(Cls1) # revealed: (<class 'Cls1'>, @Todo(StarredExpression), <class 'object'>)
|
||||
reveal_mro(Cls1) # revealed: (<class 'Cls1'>, <class 'Base'>, <class 'object'>)
|
||||
|
||||
# Unpacking a dict for the namespace - the dict contents are not tracked anyway
|
||||
namespace = {"attr": 1}
|
||||
|
||||
@@ -25,3 +25,22 @@ B = bytes
|
||||
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>)
|
||||
```
|
||||
|
||||
## Starred bases
|
||||
|
||||
These are currently not supported, but ideally we would support them in some limited situations.
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
bases = (A, B, C)
|
||||
|
||||
class Foo(*bases): ...
|
||||
|
||||
# revealed: (<class 'Foo'>, @Todo(StarredExpression), <class 'object'>)
|
||||
reveal_mro(Foo)
|
||||
```
|
||||
|
||||
@@ -1016,6 +1016,108 @@ class Color(Enum):
|
||||
reveal_type(Color.RED != Color.RED) # revealed: bool
|
||||
```
|
||||
|
||||
## Generic enums are invalid
|
||||
|
||||
Enum classes cannot be generic. Python does not support generic enums, and attempting to create one
|
||||
will result in a `TypeError` at runtime.
|
||||
|
||||
### PEP 695 syntax
|
||||
|
||||
Using PEP 695 type parameters on an enum is invalid:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `E` cannot be generic"
|
||||
class E[T](Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
```
|
||||
|
||||
### Legacy `Generic` base class
|
||||
|
||||
Inheriting from both `Enum` and `Generic[T]` is also invalid:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `F` cannot be generic"
|
||||
class F(Enum, Generic[T]):
|
||||
A = 1
|
||||
B = 2
|
||||
```
|
||||
|
||||
### Swapped order (`Generic` first)
|
||||
|
||||
The order of bases doesn't matter; it's still invalid:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `G` cannot be generic"
|
||||
class G(Generic[T], Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
```
|
||||
|
||||
### Enum subclasses
|
||||
|
||||
Subclasses of enum base classes also cannot be generic:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum, IntEnum
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyIntEnum` cannot be generic"
|
||||
class MyIntEnum[T](IntEnum):
|
||||
A = 1
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyFlagEnum` cannot be generic"
|
||||
class MyFlagEnum(IntEnum, Generic[T]):
|
||||
A = 1
|
||||
```
|
||||
|
||||
### Custom enum base class
|
||||
|
||||
Even with custom enum subclasses that don't have members, they cannot be made generic:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class MyEnumBase(Enum):
|
||||
def some_method(self) -> None: ...
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyEnum` cannot be generic"
|
||||
class MyEnum[T](MyEnumBase):
|
||||
A = 1
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Typing spec: <https://typing.python.org/en/latest/spec/enums.html>
|
||||
|
||||
@@ -43,13 +43,11 @@ reveal_type(len((1,))) # revealed: Literal[1]
|
||||
reveal_type(len((1, 2))) # revealed: Literal[2]
|
||||
reveal_type(len(tuple())) # revealed: Literal[0]
|
||||
|
||||
# TODO: Handle star unpacks; Should be: Literal[0]
|
||||
reveal_type(len((*[],))) # revealed: Literal[1]
|
||||
reveal_type(len((*[],))) # revealed: Literal[0]
|
||||
|
||||
# fmt: off
|
||||
|
||||
# TODO: Handle star unpacks; Should be: Literal[1]
|
||||
reveal_type(len( # revealed: Literal[2]
|
||||
reveal_type(len( # revealed: Literal[1]
|
||||
(
|
||||
*[],
|
||||
1,
|
||||
@@ -58,11 +56,8 @@ reveal_type(len( # revealed: Literal[2]
|
||||
|
||||
# fmt: on
|
||||
|
||||
# TODO: Handle star unpacks; Should be: Literal[2]
|
||||
reveal_type(len((*[], 1, 2))) # revealed: Literal[3]
|
||||
|
||||
# TODO: Handle star unpacks; Should be: Literal[0]
|
||||
reveal_type(len((*[], *{}))) # revealed: Literal[2]
|
||||
reveal_type(len((*[], 1, 2))) # revealed: Literal[2]
|
||||
reveal_type(len((*[], *{}))) # revealed: Literal[0]
|
||||
```
|
||||
|
||||
Tuple subclasses:
|
||||
|
||||
@@ -797,7 +797,7 @@ class B(A):
|
||||
pass
|
||||
|
||||
class C[T]:
|
||||
def check(x: object) -> TypeIs[T]:
|
||||
def check(self, x: object) -> TypeIs[T]:
|
||||
# this is a bad check, but we only care about it type-checking
|
||||
return False
|
||||
|
||||
@@ -835,7 +835,7 @@ class B(A):
|
||||
pass
|
||||
|
||||
class C[T]:
|
||||
def check(x: object) -> TypeGuard[T]:
|
||||
def check(self, x: object) -> TypeGuard[T]:
|
||||
# this is a bad check, but we only care about it type-checking
|
||||
return False
|
||||
|
||||
|
||||
@@ -69,6 +69,30 @@ def call_with_args(y: object, a: int, b: str) -> object:
|
||||
return None
|
||||
```
|
||||
|
||||
## Narrowing with named expressions (walrus operator)
|
||||
|
||||
When `callable()` is used with a named expression, the target of the named expression should be
|
||||
narrowed.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
class Foo:
|
||||
func: Any | None
|
||||
|
||||
def f(foo: Foo):
|
||||
first = getattr(foo, "func", None)
|
||||
if callable(first):
|
||||
reveal_type(first) # revealed: Any & Top[(...) -> object]
|
||||
else:
|
||||
reveal_type(first) # revealed: (Any & ~Top[(...) -> object]) | None
|
||||
|
||||
if callable(second := getattr(foo, "func", None)):
|
||||
reveal_type(second) # revealed: Any & Top[(...) -> object]
|
||||
else:
|
||||
reveal_type(second) # revealed: (Any & ~Top[(...) -> object]) | None
|
||||
```
|
||||
|
||||
## Assignability of narrowed callables
|
||||
|
||||
A narrowed callable `Top[Callable[..., object]]` should be assignable to `Callable[..., Any]`. This
|
||||
|
||||
@@ -580,3 +580,19 @@ def test(a: Any, items: list[T]) -> None:
|
||||
if isinstance(v, dict):
|
||||
cast(T, v) # no panic
|
||||
```
|
||||
|
||||
## Narrowing with named expressions (walrus operator)
|
||||
|
||||
When `isinstance()` is used with a named expression, the target of the named expression should be
|
||||
narrowed.
|
||||
|
||||
```py
|
||||
def get_value() -> int | str:
|
||||
return 1
|
||||
|
||||
def f():
|
||||
if isinstance(x := get_value(), int):
|
||||
reveal_type(x) # revealed: int
|
||||
else:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
@@ -347,3 +347,19 @@ def _(x: LiteralString):
|
||||
else:
|
||||
reveal_type(x) # revealed: LiteralString & ~Literal[""]
|
||||
```
|
||||
|
||||
## Narrowing with named expressions (walrus operator)
|
||||
|
||||
When a truthiness check is used with a named expression, the target of the named expression should
|
||||
be narrowed.
|
||||
|
||||
```py
|
||||
def get_value() -> str | None:
|
||||
return "hello"
|
||||
|
||||
def f():
|
||||
if x := get_value():
|
||||
reveal_type(x) # revealed: str & ~AlwaysFalsy
|
||||
else:
|
||||
reveal_type(x) # revealed: (str & ~AlwaysTruthy) | None
|
||||
```
|
||||
|
||||
@@ -70,6 +70,47 @@ def _(x: A | B, y: A | C):
|
||||
reveal_type(y) # revealed: A
|
||||
```
|
||||
|
||||
## The top materialization is used for generic classes
|
||||
|
||||
```py
|
||||
# list is invariant
|
||||
def f(x: list[int] | None):
|
||||
if type(x) is list:
|
||||
reveal_type(x) # revealed: list[int]
|
||||
else:
|
||||
reveal_type(x) # revealed: list[int] | None
|
||||
|
||||
if type(x) is not list:
|
||||
reveal_type(x) # revealed: list[int] | None
|
||||
else:
|
||||
reveal_type(x) # revealed: list[int]
|
||||
|
||||
# frozenset is covariant
|
||||
def g(x: frozenset[bytes] | None):
|
||||
if type(x) is frozenset:
|
||||
reveal_type(x) # revealed: frozenset[bytes]
|
||||
else:
|
||||
reveal_type(x) # revealed: frozenset[bytes] | None
|
||||
|
||||
if type(x) is not frozenset:
|
||||
reveal_type(x) # revealed: frozenset[bytes] | None
|
||||
else:
|
||||
reveal_type(x) # revealed: frozenset[bytes]
|
||||
|
||||
def h(x: object):
|
||||
if type(x) is list:
|
||||
reveal_type(x) # revealed: Top[list[Unknown]]
|
||||
elif type(x) is frozenset:
|
||||
reveal_type(x) # revealed: frozenset[object]
|
||||
else:
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
if type(x) is not list and type(x) is not frozenset:
|
||||
reveal_type(x) # revealed: object
|
||||
else:
|
||||
reveal_type(x) # revealed: Top[list[Unknown]] | frozenset[object]
|
||||
```
|
||||
|
||||
## No narrowing for `type(x) is C[int]`
|
||||
|
||||
At runtime, `type(x)` will never return a generic alias object (only ever a class-literal object),
|
||||
@@ -234,8 +275,7 @@ An early version of <https://github.com/astral-sh/ruff/pull/19920> caused us to
|
||||
```py
|
||||
def _(val):
|
||||
if type(val) is tuple:
|
||||
# TODO: better would be `Unknown & tuple[object, ...]`
|
||||
reveal_type(val) # revealed: Unknown & tuple[Unknown, ...]
|
||||
reveal_type(val) # revealed: Unknown & tuple[object, ...]
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
@@ -14,8 +14,8 @@ def _(
|
||||
b: TypeIs[str | int],
|
||||
c: TypeGuard[bool],
|
||||
d: TypeIs[tuple[TypeOf[bytes]]],
|
||||
e: TypeGuard, # error: [invalid-type-form]
|
||||
f: TypeIs, # error: [invalid-type-form]
|
||||
e: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
||||
f: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
||||
):
|
||||
reveal_type(a) # revealed: TypeGuard[str]
|
||||
reveal_type(b) # revealed: TypeIs[str | int]
|
||||
@@ -46,12 +46,23 @@ A user-defined type guard must accept at least one positional argument (in addit
|
||||
for non-static methods).
|
||||
|
||||
```pyi
|
||||
from typing import Any, TypeVar
|
||||
from typing_extensions import TypeGuard, TypeIs
|
||||
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
T = TypeVar("T")
|
||||
|
||||
# Multiple parameters are allowed
|
||||
def is_str_list(val: list[object], allow_empty: bool) -> TypeGuard[list[str]]: ...
|
||||
def is_set_of(val: set[Any], type: type[T]) -> TypeGuard[set[T]]: ...
|
||||
def is_two_element_tuple(val: tuple[object, ...], a: str, b: str) -> TypeIs[tuple[str, str]]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _() -> TypeGuard[str]: ...
|
||||
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
# error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(*args) -> TypeGuard[str]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
def _(**kwargs) -> TypeIs[str]: ...
|
||||
|
||||
class _:
|
||||
@@ -63,14 +74,14 @@ class _:
|
||||
def _(a) -> TypeIs[str]: ...
|
||||
|
||||
# errors
|
||||
def _(self) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _(self, /, *, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _(self) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(self, /, *, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
@classmethod
|
||||
def _(cls) -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _(cls) -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
@classmethod
|
||||
def _() -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _() -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
@staticmethod
|
||||
def _(*, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _(*, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
```
|
||||
|
||||
For `TypeIs` functions, the narrowed type must be assignable to the declared type of that parameter,
|
||||
@@ -86,10 +97,10 @@ def _(a: tuple[object]) -> TypeIs[tuple[str]]: ...
|
||||
def _(a: str | Any) -> TypeIs[str]: ...
|
||||
def _(a) -> TypeIs[str]: ...
|
||||
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
# error: [invalid-type-guard-definition] "Narrowed type `str` is not assignable to the declared parameter type `int`"
|
||||
def _(a: int) -> TypeIs[str]: ...
|
||||
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
# error: [invalid-type-guard-definition] "Narrowed type `int` is not assignable to the declared parameter type `bool | str`"
|
||||
def _(a: bool | str) -> TypeIs[int]: ...
|
||||
```
|
||||
|
||||
@@ -107,12 +118,14 @@ class C:
|
||||
@classmethod
|
||||
def g(cls, x: object) -> TypeGuard[int]:
|
||||
return True
|
||||
# TODO: this could error at definition time
|
||||
def h(self) -> TypeGuard[str]:
|
||||
|
||||
def h(
|
||||
self,
|
||||
) -> TypeGuard[str]: # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
return True
|
||||
# TODO: this could error at definition time
|
||||
|
||||
@classmethod
|
||||
def j(cls) -> TypeGuard[int]:
|
||||
def j(cls) -> TypeGuard[int]: # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
return True
|
||||
|
||||
def _(x: object):
|
||||
@@ -221,7 +234,7 @@ def g(a: object) -> TypeIs[int]:
|
||||
return True
|
||||
|
||||
def _(d: Any):
|
||||
if f(): # error: [missing-argument]
|
||||
if f(): # error: [missing-argument] "No argument provided for required parameter `a` of function `f`"
|
||||
...
|
||||
|
||||
if g(*d):
|
||||
@@ -230,7 +243,7 @@ def _(d: Any):
|
||||
if f("foo"): # TODO: error: [invalid-type-guard-call]
|
||||
...
|
||||
|
||||
if g(a=d): # error: [invalid-type-guard-call]
|
||||
if g(a=d): # error: [invalid-type-guard-call] "Type guard call does not have a target"
|
||||
...
|
||||
```
|
||||
|
||||
@@ -499,3 +512,32 @@ def _(x: object):
|
||||
if f(x) and (g(x) or h(x)):
|
||||
reveal_type(x) # revealed: B | (A & C)
|
||||
```
|
||||
|
||||
## Narrowing with named expressions (walrus operator)
|
||||
|
||||
When a type guard is used with a named expression, the target of the named expression should be
|
||||
narrowed.
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeGuard, TypeIs
|
||||
|
||||
def is_str(x: object) -> TypeIs[str]:
|
||||
return isinstance(x, str)
|
||||
|
||||
def guard_str(x: object) -> TypeGuard[str]:
|
||||
return isinstance(x, str)
|
||||
|
||||
def get_value() -> int | str:
|
||||
return 1
|
||||
|
||||
def f():
|
||||
if is_str(x := get_value()):
|
||||
reveal_type(x) # revealed: str
|
||||
else:
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
if guard_str(y := get_value()):
|
||||
reveal_type(y) # revealed: str
|
||||
else:
|
||||
reveal_type(y) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Subscripts involving type aliases
|
||||
|
||||
Aliases are expanded during analysis of subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
def f(
|
||||
implicit_tuple: ImplicitTuple,
|
||||
pep_613_tuple: PEP613Tuple,
|
||||
pep_695_tuple: PEP695Tuple,
|
||||
implicit_zero: ImplicitZero,
|
||||
pep_613_zero: PEP613Zero,
|
||||
pep_695_zero: PEP695Zero,
|
||||
):
|
||||
reveal_type(implicit_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(implicit_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_613_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_613_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_695_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_695_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_695_zero]) # revealed: str
|
||||
```
|
||||
@@ -106,5 +106,5 @@ class Bar:
|
||||
def f(x: Foo):
|
||||
if isinstance(x, Bar):
|
||||
# TODO: should be `int`
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
```
|
||||
|
||||
@@ -80,6 +80,17 @@ def _(m: int, n: int, s2: str):
|
||||
reveal_type(substring2) # revealed: str
|
||||
```
|
||||
|
||||
## LiteralString
|
||||
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
def f(x: LiteralString):
|
||||
reveal_type(x[0]) # revealed: LiteralString
|
||||
reveal_type(x[True]) # revealed: LiteralString
|
||||
reveal_type(x[1:42]) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
```py
|
||||
|
||||
@@ -430,5 +430,5 @@ class Bar: ...
|
||||
|
||||
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
|
||||
# TODO: should be `Foo & Bar`
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# Subscripts involving type variables
|
||||
|
||||
## TypeVar bound/constrained to a tuple/int-literal/bool-literal
|
||||
|
||||
The upper bounds of type variables are considered when analysing subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
# fmt: off
|
||||
|
||||
def f[
|
||||
BoundedTupleT: tuple[str, int, bytes],
|
||||
ConstrainedTupleT: (tuple[str, int, bytes], tuple[int, bytes, str]),
|
||||
BoundedZeroT: Literal[0],
|
||||
ConstrainedIntLiteralT: (Literal[0], Literal[1])
|
||||
](
|
||||
tuple_1: BoundedTupleT,
|
||||
tuple_2: ConstrainedTupleT,
|
||||
zero: BoundedZeroT,
|
||||
some_integer: ConstrainedIntLiteralT,
|
||||
):
|
||||
# TODO: would ideally be `tuple[str, int]`
|
||||
reveal_type(tuple_1[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_1[zero]) # revealed: str
|
||||
|
||||
# TODO: ideally this might be `str | int`,
|
||||
# but it's hard to do that without introducing false positives elsewhere
|
||||
reveal_type(tuple_1[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# TODO: would ideally be `tuple[str, int] | tuple[int, bytes]`
|
||||
reveal_type(tuple_2[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_2[zero]) # revealed: str | int
|
||||
reveal_type(tuple_2[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
## TypeVars
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class SupportsLessThan(Protocol):
|
||||
def __lt__(self, other, /) -> bool: ...
|
||||
|
||||
def f[K: SupportsLessThan](dictionary: dict[K, int], key: K):
|
||||
reveal_type(dictionary[key]) # revealed: int
|
||||
```
|
||||
|
||||
## ParamSpecs
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def decorator[**P, T](func: Callable[P, T]) -> Callable[P, T]:
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
if len(args) > 0:
|
||||
# error: [invalid-assignment]
|
||||
args = args[1:]
|
||||
|
||||
# `func` requires the full `ParamSpec` passed into `decorator`,
|
||||
# but here the first argument is skipped, so we should possibly emit an error here:
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
```
|
||||
@@ -534,4 +534,34 @@ x: list[Literal[1, 2, 3]] = list((1, 2, 3))
|
||||
reveal_type(x) # revealed: list[Literal[1, 2, 3]]
|
||||
```
|
||||
|
||||
## Tuples with starred elements
|
||||
|
||||
```py
|
||||
from typing import Literal, Sequence
|
||||
|
||||
x = (1, *range(3), 3)
|
||||
reveal_type(x) # revealed: tuple[Literal[1], *tuple[int, ...], Literal[3]]
|
||||
|
||||
y = 1, 2
|
||||
|
||||
reveal_type(("foo", *y)) # revealed: tuple[Literal["foo"], Literal[1], Literal[2]]
|
||||
|
||||
aa: tuple[list[int], ...] = ([42], *{[56], [78]}, [100])
|
||||
reveal_type(aa) # revealed: tuple[list[int], list[int], list[int], list[int]]
|
||||
|
||||
bb: tuple[list[Literal[42, 56]], ...] = ([42], *{[56, 42], [42]}, [42, 42, 56])
|
||||
reveal_type(bb) # revealed: tuple[list[Literal[42, 56]], list[Literal[42, 56]], list[Literal[42, 56]], list[Literal[42, 56]]]
|
||||
|
||||
reveal_type((*[],)) # revealed: tuple[()]
|
||||
reveal_type((42, *[], 56, *[])) # revealed: tuple[Literal[42], Literal[56]]
|
||||
|
||||
tup: Sequence[str] = (*{"foo": 42, "bar": 56},)
|
||||
|
||||
# TODO: `tuple[str, str]` would be better, given the type annotation
|
||||
reveal_type(tup) # revealed: tuple[Unknown | str, Unknown | str]
|
||||
|
||||
def f(x: list[int]):
|
||||
reveal_type((42, 56, *x, 97)) # revealed: tuple[Literal[42], Literal[56], *tuple[int, ...], Literal[97]]
|
||||
```
|
||||
|
||||
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957
|
||||
|
||||
@@ -38,6 +38,12 @@ impl PlaceExpr {
|
||||
pub(crate) fn try_from_expr<'e>(expr: impl Into<ast::ExprRef<'e>>) -> Option<Self> {
|
||||
let expr = expr.into();
|
||||
|
||||
// For named expressions (walrus operator), extract the target.
|
||||
let expr = match expr {
|
||||
ast::ExprRef::Named(named) => named.target.as_ref().into(),
|
||||
_ => expr,
|
||||
};
|
||||
|
||||
if let ast::ExprRef::Name(name) = expr {
|
||||
return Some(PlaceExpr::Symbol(Symbol::new(name.id.clone())));
|
||||
}
|
||||
|
||||
@@ -3256,12 +3256,16 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
// This is one of the few places where we want to check if there's _any_ specialization
|
||||
// where assignability holds; normally we want to check that assignability holds for
|
||||
// _all_ specializations.
|
||||
//
|
||||
// TODO: Soon we will go further, and build the actual specializations from the
|
||||
// constraint set that we get from this assignability check, instead of inferring and
|
||||
// building them in an earlier separate step.
|
||||
if argument_type
|
||||
.when_assignable_to(self.db, expected_ty, self.inferable_typevars)
|
||||
.is_never_satisfied(self.db)
|
||||
//
|
||||
// TODO: handle starred annotations, e.g. `*args: *Ts` or `*args: *tuple[int, *tuple[str, ...]]`
|
||||
if !parameter.has_starred_annotation()
|
||||
&& argument_type
|
||||
.when_assignable_to(self.db, expected_ty, self.inferable_typevars)
|
||||
.is_never_satisfied(self.db)
|
||||
{
|
||||
let positional = matches!(argument, Argument::Positional | Argument::Synthetic)
|
||||
&& !parameter.is_variadic();
|
||||
|
||||
@@ -45,10 +45,10 @@ use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, Binding, BindingContext, BoundSuperType, CallableType,
|
||||
CallableTypeKind, CallableTypes, DATACLASS_FLAGS, DataclassFlags, DataclassParams,
|
||||
DeprecatedInstance, FindLegacyTypeVarsVisitor, IntersectionBuilder, KnownInstanceType,
|
||||
ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType,
|
||||
TypeAliasType, TypeContext, TypeMapping, TypedDictParams, UnionBuilder, VarianceInferable,
|
||||
binding_type, declaration_type, determine_upper_bound,
|
||||
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, IntersectionBuilder,
|
||||
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor,
|
||||
PropertyInstanceType, TypeAliasType, TypeContext, TypeMapping, TypedDictParams, UnionBuilder,
|
||||
VarianceInferable, binding_type, declaration_type, determine_upper_bound,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,
|
||||
@@ -664,14 +664,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an unknown specialization for this class.
|
||||
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self {
|
||||
Self::Static(class) => class.unknown_specialization(db),
|
||||
Self::Dynamic(_) => ClassType::NonGeneric(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the definition of this class.
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
match self {
|
||||
@@ -2379,6 +2371,11 @@ impl<'db> StaticClassLiteral<'db> {
|
||||
|
||||
let module = parsed_module(db, self.file(db)).load(db);
|
||||
let class_stmt = self.node(db, &module);
|
||||
|
||||
if class_stmt.bases().iter().any(ast::Expr::is_starred_expr) {
|
||||
return Box::new([Type::Dynamic(DynamicType::TodoStarredExpression)]);
|
||||
}
|
||||
|
||||
let class_definition =
|
||||
semantic_index(db, self.file(db)).expect_single_definition(class_stmt);
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&INVALID_CONTEXT_MANAGER);
|
||||
registry.register_lint(&INVALID_DECLARATION);
|
||||
registry.register_lint(&INVALID_EXCEPTION_CAUGHT);
|
||||
registry.register_lint(&INVALID_GENERIC_ENUM);
|
||||
registry.register_lint(&INVALID_GENERIC_CLASS);
|
||||
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
|
||||
registry.register_lint(&INVALID_PARAMSPEC);
|
||||
@@ -956,6 +957,48 @@ declare_lint! {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for enum classes that are also generic.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Enum classes cannot be generic. Python does not support generic enums:
|
||||
/// attempting to create one will either result in an immediate `TypeError`
|
||||
/// at runtime, or will create a class that cannot be specialized in the way
|
||||
/// that a normal generic class can.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// from enum import Enum
|
||||
/// from typing import Generic, TypeVar
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
///
|
||||
/// # error: enum class cannot be generic (class creation fails with `TypeError`)
|
||||
/// class E[T](Enum):
|
||||
/// A = 1
|
||||
///
|
||||
/// # error: enum class cannot be generic (class creation fails with `TypeError`)
|
||||
/// class F(Enum, Generic[T]):
|
||||
/// A = 1
|
||||
///
|
||||
/// # error: enum class cannot be generic -- the class creation does not immediately fail...
|
||||
/// class G(Generic[T], Enum):
|
||||
/// A = 1
|
||||
///
|
||||
/// # ...but this raises `KeyError`:
|
||||
/// x: G[int]
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Enum](https://docs.python.org/3/library/enum.html)
|
||||
pub(crate) static INVALID_GENERIC_ENUM = {
|
||||
summary: "detects generic enum classes",
|
||||
status: LintStatus::stable("0.0.12"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for the creation of invalid generic classes
|
||||
|
||||
@@ -65,15 +65,15 @@ use crate::types::diagnostic::{
|
||||
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_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_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases,
|
||||
NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL,
|
||||
POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS, TypedDictDeleteErrorKind, UNDEFINED_REVEAL,
|
||||
UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE,
|
||||
UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
|
||||
hint_if_stdlib_attribute_exists_on_other_versions,
|
||||
INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
|
||||
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM,
|
||||
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
|
||||
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
|
||||
TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
|
||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR,
|
||||
USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions,
|
||||
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
|
||||
report_bad_dunder_set_call, report_bad_frozen_dataclass_inheritance,
|
||||
report_cannot_delete_typed_dict_key, report_cannot_pop_required_field_on_typed_dict,
|
||||
@@ -92,6 +92,7 @@ use crate::types::diagnostic::{
|
||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
||||
report_unsupported_binary_operation, report_unsupported_comparison,
|
||||
};
|
||||
use crate::types::enums::is_enum_class_by_inheritance;
|
||||
use crate::types::function::{
|
||||
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
||||
is_implicit_classmethod, is_implicit_staticmethod,
|
||||
@@ -105,7 +106,7 @@ use crate::types::instance::SliceLiteral;
|
||||
use crate::types::mro::{DynamicMroErrorKind, StaticMroErrorKind};
|
||||
use crate::types::newtype::NewType;
|
||||
use crate::types::subclass_of::SubclassOfInner;
|
||||
use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType};
|
||||
use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleSpecBuilder, TupleType};
|
||||
use crate::types::typed_dict::{
|
||||
TypedDictAssignmentKind, validate_typed_dict_constructor, validate_typed_dict_dict_literal,
|
||||
validate_typed_dict_key_assignment,
|
||||
@@ -586,6 +587,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if self.db().should_check_file(self.file()) {
|
||||
self.check_static_class_definitions();
|
||||
self.check_overloaded_functions(node);
|
||||
self.check_type_guard_definitions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,10 +638,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// (2) Check that the class is not an enum and generic
|
||||
if is_enum_class_by_inheritance(self.db(), class)
|
||||
&& class.generic_context(self.db()).is_some()
|
||||
{
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_GENERIC_ENUM, class_node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Enum class `{}` cannot be generic",
|
||||
class.name(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let is_named_tuple =
|
||||
CodeGeneratorKind::NamedTuple.matches(self.db(), class.into(), None);
|
||||
|
||||
// (2) If it's a `NamedTuple` class, check that no field without a default value
|
||||
// (3) If it's a `NamedTuple` class, check that no field without a default value
|
||||
// appears after a field with a default value.
|
||||
if is_named_tuple {
|
||||
let mut field_with_default_encountered = None;
|
||||
@@ -680,7 +694,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let mut disjoint_bases = IncompatibleBases::default();
|
||||
|
||||
// (3) Iterate through the class's explicit bases to check for various possible errors:
|
||||
// (4) Iterate through the class's explicit bases to check for various possible errors:
|
||||
// - Check for inheritance from plain `Generic`,
|
||||
// - Check for inheritance from a `@final` classes
|
||||
// - If the class is a protocol class: check for inheritance from a non-protocol class
|
||||
@@ -794,7 +808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (4) Check that the class's MRO is resolvable
|
||||
// (5) Check that the class's MRO is resolvable
|
||||
match class.try_mro(self.db(), None) {
|
||||
Err(mro_error) => match mro_error.reason() {
|
||||
StaticMroErrorKind::DuplicateBases(duplicates) => {
|
||||
@@ -865,7 +879,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (5) Check that @total_ordering has a valid ordering method in the MRO
|
||||
// (6) Check that @total_ordering has a valid ordering method in the MRO
|
||||
if class.total_ordering(self.db()) && !class.has_ordering_method_in_mro(self.db(), None)
|
||||
{
|
||||
// Find the @total_ordering decorator to report the diagnostic at its location
|
||||
@@ -884,7 +898,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (6) Check that the class's metaclass can be determined without error.
|
||||
// (7) Check that the class's metaclass can be determined without error.
|
||||
if let Err(metaclass_error) = class.try_metaclass(self.db()) {
|
||||
match metaclass_error.reason() {
|
||||
MetaclassErrorKind::Cycle => {
|
||||
@@ -960,7 +974,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (7) Check that the class arguments matches the arguments of the
|
||||
// (8) Check that the class arguments matches the arguments of the
|
||||
// base class `__init_subclass__` method.
|
||||
if let Some(args) = class_node.arguments.as_deref() {
|
||||
let call_args: CallArguments = args
|
||||
@@ -1000,7 +1014,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (8) If the class is generic, verify that its generic context does not violate any of
|
||||
// (9) If the class is generic, verify that its generic context does not violate any of
|
||||
// the typevar scoping rules.
|
||||
if let (Some(legacy), Some(inherited)) = (
|
||||
class.legacy_generic_context(self.db()),
|
||||
@@ -1079,7 +1093,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (9) Check that a dataclass does not have more than one `KW_ONLY`.
|
||||
// (10) Check that a dataclass does not have more than one `KW_ONLY`.
|
||||
if let Some(field_policy @ CodeGeneratorKind::DataclassLike(_)) =
|
||||
CodeGeneratorKind::from_class(self.db(), class.into(), None)
|
||||
{
|
||||
@@ -1114,7 +1128,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
// (10) Check for violations of the Liskov Substitution Principle,
|
||||
// (11) Check for violations of the Liskov Substitution Principle,
|
||||
// and for violations of other rules relating to invalid overrides of some sort.
|
||||
overrides::check_class(&self.context, class);
|
||||
|
||||
@@ -1122,7 +1136,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
protocol.validate_members(&self.context);
|
||||
}
|
||||
|
||||
// (11) If it's a `TypedDict` class, check that it doesn't include any invalid
|
||||
// (12) If it's a `TypedDict` class, check that it doesn't include any invalid
|
||||
// statements: https://typing.python.org/en/latest/spec/typeddict.html#class-based-syntax
|
||||
//
|
||||
// The body of the class definition defines the items of the `TypedDict` type. It
|
||||
@@ -1439,6 +1453,85 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that all type guard function definitions have at least one positional parameter
|
||||
/// (in addition to `self`/`cls` for methods), and for `TypeIs`, that the narrowed type is
|
||||
/// assignable to the declared type of that parameter.
|
||||
fn check_type_guard_definitions(&mut self) {
|
||||
for (definition, ty) in self.declarations.iter() {
|
||||
// Only check actual function definitions, not imports.
|
||||
let DefinitionKind::Function(function_ref) = definition.kind(self.db()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(function) = ty.inner_type().as_function_literal() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for overload in function.iter_overloads_and_implementation(self.db()) {
|
||||
let signature = overload.signature(self.db());
|
||||
let return_ty = signature.return_ty;
|
||||
|
||||
// Check if this is a `TypeIs` or `TypeGuard` return type.
|
||||
let (type_guard_form_name, narrowed_type) = match return_ty {
|
||||
Type::TypeIs(type_is) => ("TypeIs", Some(type_is.return_type(self.db()))),
|
||||
Type::TypeGuard(_) => ("TypeGuard", None),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let function_node = function_ref.node(self.module());
|
||||
|
||||
// The return type annotation must exist since we matched `TypeIs`/`TypeGuard`.
|
||||
let Some(returns_expr) = function_node.returns.as_deref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Check if this is a non-static method (first parameter is implicit `self`/`cls`).
|
||||
let is_method = self
|
||||
.index
|
||||
.class_definition_of_method(
|
||||
overload.body_scope(self.db()).file_scope_id(self.db()),
|
||||
)
|
||||
.is_some();
|
||||
let has_implicit_receiver = is_method && !overload.is_staticmethod(self.db());
|
||||
|
||||
// Find the first positional parameter to narrow (skip implicit `self`/`cls`).
|
||||
let positional_params: Vec<_> = signature.parameters().positional().collect();
|
||||
let first_narrowed_param_index = usize::from(has_implicit_receiver);
|
||||
let first_narrowed_param = positional_params.get(first_narrowed_param_index);
|
||||
|
||||
let Some(first_narrowed_param) = first_narrowed_param else {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_GUARD_DEFINITION, returns_expr)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`{type_guard_form_name}` function must have a parameter to narrow"
|
||||
));
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
// For `TypeIs`, check that the narrowed type is assignable to the parameter type.
|
||||
if let Some(narrowed_ty) = narrowed_type {
|
||||
let param_ty = first_narrowed_param.annotated_type();
|
||||
if !narrowed_ty.is_assignable_to(self.db(), param_ty) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_GUARD_DEFINITION, returns_expr)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Narrowed type `{narrowed}` is not assignable \
|
||||
to the declared parameter type `{param}`",
|
||||
narrowed = narrowed_ty.display(self.db()),
|
||||
param = param_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_region_definition(&mut self, definition: Definition<'db>) {
|
||||
match definition.kind(self.db()) {
|
||||
DefinitionKind::Function(function) => {
|
||||
@@ -8154,7 +8247,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
ast::Expr::If(if_expression) => self.infer_if_expression(if_expression, tcx),
|
||||
ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression),
|
||||
ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression, tcx),
|
||||
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
|
||||
ast::Expr::Starred(starred) => self.infer_starred_expression(starred, tcx),
|
||||
ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression),
|
||||
ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from),
|
||||
ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression),
|
||||
@@ -8399,7 +8492,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
parenthesized: _,
|
||||
} = tuple;
|
||||
|
||||
// Remove any union elements of that are unrelated to the tuple type.
|
||||
// Remove any union elements of the annotation that are unrelated to the tuple type.
|
||||
let tcx = tcx.map(|annotation| {
|
||||
let inferable = KnownClass::Tuple
|
||||
.try_to_class_literal(self.db())
|
||||
@@ -8413,16 +8506,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
)
|
||||
});
|
||||
|
||||
let mut is_homogeneous_tuple_annotation = false;
|
||||
|
||||
let annotated_tuple = tcx
|
||||
.known_specialization(self.db(), KnownClass::Tuple)
|
||||
.and_then(|specialization| {
|
||||
specialization
|
||||
let spec = specialization
|
||||
.tuple(self.db())
|
||||
.expect("the specialization of `KnownClass::Tuple` must have a tuple spec")
|
||||
.resize(self.db(), TupleLength::Fixed(elts.len()))
|
||||
.ok()
|
||||
.expect("the specialization of `KnownClass::Tuple` must have a tuple spec");
|
||||
|
||||
if let Tuple::Variable(tuple) = spec
|
||||
&& tuple.prefix_elements().is_empty()
|
||||
&& tuple.suffix_elements().is_empty()
|
||||
{
|
||||
is_homogeneous_tuple_annotation = true;
|
||||
}
|
||||
|
||||
spec.resize(self.db(), TupleLength::Fixed(elts.len())).ok()
|
||||
});
|
||||
|
||||
// TODO: this is a simplification for now.
|
||||
//
|
||||
// It might be possible to use the type context where the annotation is not a pure-homogeneous
|
||||
// tuple and the actual tuple has starred elements in it. It seems complex to reason about,
|
||||
// though, and unlikely to come up much.
|
||||
let can_use_type_context =
|
||||
is_homogeneous_tuple_annotation || elts.iter().all(|elt| !elt.is_starred_expr());
|
||||
|
||||
let mut annotated_elt_tys = annotated_tuple
|
||||
.as_ref()
|
||||
.map(Tuple::all_elements)
|
||||
@@ -8431,12 +8541,59 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.copied();
|
||||
|
||||
let db = self.db();
|
||||
let element_types = elts.iter().map(|element| {
|
||||
let annotated_elt_ty = annotated_elt_tys.next();
|
||||
self.infer_expression(element, TypeContext::new(annotated_elt_ty))
|
||||
});
|
||||
|
||||
Type::heterogeneous_tuple(db, element_types)
|
||||
let mut infer_element = |elt: &ast::Expr| {
|
||||
let ctx = if can_use_type_context {
|
||||
let annotated_elt_ty = annotated_elt_tys.by_ref().next();
|
||||
let expected = if elt.is_starred_expr() {
|
||||
let expected_element = annotated_elt_ty.unwrap_or_else(Type::object);
|
||||
Some(KnownClass::Iterable.to_specialized_instance(db, &[expected_element]))
|
||||
} else {
|
||||
annotated_elt_ty
|
||||
};
|
||||
TypeContext::new(expected)
|
||||
} else {
|
||||
TypeContext::default()
|
||||
};
|
||||
self.infer_expression(elt, ctx)
|
||||
};
|
||||
|
||||
let mut builder = TupleSpecBuilder::with_capacity(elts.len());
|
||||
|
||||
for element in elts {
|
||||
if let ast::Expr::Starred(starred) = element {
|
||||
let element_type = infer_element(element);
|
||||
// Fine to use `iterate` rather than `try_iterate` here:
|
||||
// errors from iterating over something not iterable will have been
|
||||
// emitted in the `infer_element` call above.
|
||||
let mut spec = element_type.iterate(db).into_owned();
|
||||
|
||||
let known_length = match &*starred.value {
|
||||
ast::Expr::List(ast::ExprList { elts, .. })
|
||||
| ast::Expr::Set(ast::ExprSet { elts, .. }) => elts
|
||||
.iter()
|
||||
.all(|elt| !elt.is_starred_expr())
|
||||
.then_some(elts.len()),
|
||||
ast::Expr::Dict(ast::ExprDict { items, .. }) => items
|
||||
.iter()
|
||||
.all(|item| item.key.is_some())
|
||||
.then_some(items.len()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(known_length) = known_length {
|
||||
spec = spec
|
||||
.resize(db, TupleLength::Fixed(known_length))
|
||||
.unwrap_or(spec);
|
||||
}
|
||||
|
||||
builder = builder.concat(db, &spec);
|
||||
} else {
|
||||
builder.push(infer_element(element));
|
||||
}
|
||||
}
|
||||
|
||||
Type::tuple(TupleType::new(db, &builder.build()))
|
||||
}
|
||||
|
||||
fn infer_list_expression(&mut self, list: &ast::ExprList, tcx: TypeContext<'db>) -> Type<'db> {
|
||||
@@ -8762,7 +8919,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// assignments from the type context to potentially _narrow_ the inferred type,
|
||||
// by avoiding literal promotion.
|
||||
let elt_ty_identity = elt_ty.identity(self.db());
|
||||
let elt_tcx = elt_tcx_constraints.get(&elt_ty_identity).copied();
|
||||
|
||||
// If the element is a starred expression, we want to apply the type context to each element
|
||||
// in the unpacked expression (which we will store as a tuple when inferring it). We
|
||||
// therefore wrap the type context in an `tuple[T, ...]` specialization.
|
||||
let elt_tcx = elt_tcx_constraints
|
||||
.get(&elt_ty_identity)
|
||||
.copied()
|
||||
.map(|tcx| {
|
||||
if elt.is_starred_expr() && collection_class != KnownClass::Dict {
|
||||
Type::homogeneous_tuple(self.db(), tcx)
|
||||
} else {
|
||||
tcx
|
||||
}
|
||||
});
|
||||
|
||||
let inferred_elt_ty =
|
||||
infer_elt_expression(self, (i, elt, TypeContext::new(elt_tcx)));
|
||||
@@ -8781,7 +8951,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let inferred_elt_ty =
|
||||
inferred_elt_ty.promote_literals(self.db(), TypeContext::new(elt_tcx));
|
||||
|
||||
builder.infer(Type::TypeVar(elt_ty), inferred_elt_ty).ok()?;
|
||||
builder
|
||||
.infer(
|
||||
Type::TypeVar(elt_ty),
|
||||
if elt.is_starred_expr() {
|
||||
inferred_elt_ty
|
||||
.iterate(self.db())
|
||||
.homogeneous_element_type(self.db())
|
||||
} else {
|
||||
inferred_elt_ty
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9668,7 +9849,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
||||
fn infer_starred_expression(
|
||||
&mut self,
|
||||
starred: &ast::ExprStarred,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let ast::ExprStarred {
|
||||
range: _,
|
||||
node_index: _,
|
||||
@@ -9676,17 +9861,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
ctx: _,
|
||||
} = starred;
|
||||
|
||||
let iterable_type = self.infer_expression(value, TypeContext::default());
|
||||
let db = self.db();
|
||||
let iterable_type = self.infer_expression(value, tcx);
|
||||
iterable_type
|
||||
.try_iterate(self.db())
|
||||
.map(|tuple| tuple.homogeneous_element_type(self.db()))
|
||||
.try_iterate(db)
|
||||
.map(|spec| Type::tuple(TupleType::new(db, &spec)))
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
|
||||
err.fallback_element_type(self.db())
|
||||
});
|
||||
|
||||
// TODO
|
||||
Type::Dynamic(DynamicType::TodoStarredExpression)
|
||||
Type::homogeneous_tuple(db, err.fallback_element_type(db))
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
|
||||
@@ -13138,16 +13321,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let value_node = subscript.value.as_ref();
|
||||
|
||||
let inferred = match (value_ty, slice_ty) {
|
||||
(Type::Dynamic(_) | Type::Never, _) => Some(value_ty),
|
||||
|
||||
(Type::TypeAlias(alias), _) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
alias.value_type(self.db()),
|
||||
slice_ty,
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(_, Type::TypeAlias(alias)) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
value_ty,
|
||||
alias.value_type(self.db()),
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(Type::Union(union), _) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
|
||||
})),
|
||||
|
||||
(_, Type::Union(union)) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, value_ty, *element, expr_context)
|
||||
})),
|
||||
|
||||
// TODO: we can map over the intersection and fold the results back into an intersection,
|
||||
// but we need to make sure we avoid emitting a diagnostic if one positive element has a `__getitem__`
|
||||
// method but another does not. This means `infer_subscript_expression_types`
|
||||
// needs to return a `Result` rather than eagerly emitting diagnostics.
|
||||
(Type::Intersection(_), _) => {
|
||||
Some(todo_type!("Subscript expressions on intersections"))
|
||||
(Type::Intersection(_), _) | (_, Type::Intersection(_)) => {
|
||||
Some(todo_type!("Subscript expressions with intersections"))
|
||||
}
|
||||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
@@ -13227,6 +13430,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}),
|
||||
|
||||
(Type::LiteralString, Type::IntLiteral(_) | Type::BooleanLiteral(_)) => {
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::NominalInstance(nominal))
|
||||
if nominal.slice_literal(db).is_some() =>
|
||||
{
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
// Ex) Given `b"value"[1]`, return `97` (i.e., `ord(b"a")`)
|
||||
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
||||
i32::try_from(i64_int).ok().map(|i32_int| {
|
||||
@@ -13358,7 +13571,39 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Some(todo_type!("Inference of subscript on special form"))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
(
|
||||
Type::FunctionLiteral(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::BoundMethod(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::Callable(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeGuard(_)
|
||||
| Type::TypedDict(_)
|
||||
| Type::NewTypeInstance(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::TypeVar(_) // TODO: more complex logic required here!
|
||||
| Type::KnownBoundMethod(_),
|
||||
_,
|
||||
) => None,
|
||||
};
|
||||
|
||||
if let Some(inferred) = inferred {
|
||||
|
||||
@@ -167,9 +167,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
ast::Expr::StringLiteral(string) => self.infer_string_annotation_expression(string),
|
||||
|
||||
// Annotation expressions also get special handling for `*args` and `**kwargs`.
|
||||
ast::Expr::Starred(starred) => {
|
||||
TypeAndQualifiers::declared(self.infer_starred_expression(starred))
|
||||
}
|
||||
ast::Expr::Starred(starred) => TypeAndQualifiers::declared(
|
||||
self.infer_starred_expression(starred, TypeContext::default()),
|
||||
),
|
||||
|
||||
ast::Expr::BytesLiteral(bytes) => {
|
||||
if let Some(builder) = self
|
||||
|
||||
@@ -454,13 +454,6 @@ fn merge_constraints_or<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
fn place_expr(expr: &ast::Expr) -> Option<PlaceExpr> {
|
||||
match expr {
|
||||
ast::Expr::Named(named) => PlaceExpr::try_from_expr(named.target.as_ref()),
|
||||
_ => PlaceExpr::try_from_expr(expr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if it is possible for any two inhabitants of the given types to
|
||||
/// compare equal to each other; otherwise return `false`.
|
||||
fn could_compare_equal<'db>(db: &'db dyn Db, left_ty: Type<'db>, right_ty: Type<'db>) -> bool {
|
||||
@@ -721,7 +714,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
expr: &ast::Expr,
|
||||
is_positive: bool,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let target = place_expr(expr)?;
|
||||
let target = PlaceExpr::try_from_expr(expr)?;
|
||||
let place = self.expect_place(&target);
|
||||
|
||||
let ty = if is_positive {
|
||||
@@ -1030,15 +1023,15 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
if matches!(&**ops, [ast::CmpOp::Is | ast::CmpOp::IsNot])
|
||||
&& let ast::Expr::Subscript(subscript) = &**left
|
||||
&& let Type::Union(union) = inference.expression_type(&*subscript.value)
|
||||
&& let Some(subscript_place_expr) = place_expr(&subscript.value)
|
||||
&& let Some(subscript_place_expr) = PlaceExpr::try_from_expr(&subscript.value)
|
||||
&& let Type::IntLiteral(index) = inference.expression_type(&*subscript.slice)
|
||||
&& let Ok(index) = i32::try_from(index)
|
||||
&& let rhs_ty = inference.expression_type(&comparators[0])
|
||||
&& rhs_ty.is_singleton(self.db)
|
||||
{
|
||||
let is_positive_check = is_positive == (ops[0] == ast::CmpOp::Is);
|
||||
let filtered: Vec<_> = union
|
||||
.elements(self.db)
|
||||
let union_elements = union.elements(self.db);
|
||||
let filtered: Vec<_> = union_elements
|
||||
.iter()
|
||||
.filter(|elem| {
|
||||
elem.as_nominal_instance()
|
||||
@@ -1056,11 +1049,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
})
|
||||
.copied()
|
||||
.collect();
|
||||
if filtered.len() < union.elements(self.db).len() {
|
||||
if filtered.len() < union_elements.len() {
|
||||
let place = self.expect_place(&subscript_place_expr);
|
||||
constraints.insert(
|
||||
place,
|
||||
NarrowingConstraint::regular(UnionType::from_elements(self.db, filtered)),
|
||||
NarrowingConstraint::typeguard(UnionType::from_elements(self.db, filtered)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1122,7 +1115,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
// reveal_type(u) # revealed: Bar
|
||||
if matches!(&**ops, [ast::CmpOp::In | ast::CmpOp::NotIn])
|
||||
&& let Type::StringLiteral(key) = inference.expression_type(&**left)
|
||||
&& let Some(rhs_place_expr) = place_expr(&comparators[0])
|
||||
&& let Some(rhs_place_expr) = PlaceExpr::try_from_expr(&comparators[0])
|
||||
&& let rhs_type = inference.expression_type(&comparators[0])
|
||||
&& is_typeddict_or_union_with_typeddicts(self.db, rhs_type)
|
||||
{
|
||||
@@ -1190,7 +1183,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
| ast::Expr::Attribute(_)
|
||||
| ast::Expr::Subscript(_)
|
||||
| ast::Expr::Named(_) => {
|
||||
if let Some(left) = place_expr(left)
|
||||
if let Some(left) = PlaceExpr::try_from_expr(left)
|
||||
&& let Some(ty) =
|
||||
self.evaluate_expr_compare_op(lhs_ty, rhs_ty, *op, is_positive)
|
||||
{
|
||||
@@ -1215,7 +1208,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
};
|
||||
|
||||
let target = match &**args {
|
||||
[first] => match place_expr(first) {
|
||||
[first] => match PlaceExpr::try_from_expr(first) {
|
||||
Some(target) => target,
|
||||
None => continue,
|
||||
},
|
||||
@@ -1244,7 +1237,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
constraints.insert(
|
||||
place,
|
||||
NarrowingConstraint::regular(
|
||||
Type::instance(self.db, rhs_class.unknown_specialization(self.db))
|
||||
Type::instance(self.db, rhs_class.top_materialization(self.db))
|
||||
.negate_if(self.db, !is_positive),
|
||||
),
|
||||
);
|
||||
@@ -1264,7 +1257,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
| ast::Expr::Named(_)
|
||||
) =>
|
||||
{
|
||||
if let Some(right_place) = place_expr(right)
|
||||
if let Some(right_place) = PlaceExpr::try_from_expr(right)
|
||||
// Swap lhs_ty and rhs_ty since we're narrowing the right operand
|
||||
&& let Some(ty) =
|
||||
self.evaluate_expr_compare_op(rhs_ty, lhs_ty, *op, is_positive)
|
||||
@@ -1315,7 +1308,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
|
||||
// Narrow only the parts of the type that are safe to narrow based on len().
|
||||
if let Some(narrowed_ty) = Self::narrow_type_by_len(self.db, arg_ty, is_positive) {
|
||||
let target = place_expr(arg)?;
|
||||
let target = PlaceExpr::try_from_expr(arg)?;
|
||||
let place = self.expect_place(&target);
|
||||
Some(NarrowingConstraints::from_iter([(
|
||||
place,
|
||||
@@ -1329,7 +1322,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
let [first_arg, second_arg] = &*expr_call.arguments.args else {
|
||||
return None;
|
||||
};
|
||||
let first_arg = place_expr(first_arg)?;
|
||||
let first_arg = PlaceExpr::try_from_expr(first_arg)?;
|
||||
let function = function_type.known(self.db)?;
|
||||
let place = self.expect_place(&first_arg);
|
||||
|
||||
@@ -1427,7 +1420,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
singleton: ast::Singleton,
|
||||
is_positive: bool,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let subject = place_expr(subject.node_ref(self.db, self.module))?;
|
||||
let subject = PlaceExpr::try_from_expr(subject.node_ref(self.db, self.module))?;
|
||||
let place = self.expect_place(&subject);
|
||||
|
||||
let ty = match singleton {
|
||||
@@ -1456,7 +1449,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let subject = place_expr(subject.node_ref(self.db, self.module))?;
|
||||
let subject = PlaceExpr::try_from_expr(subject.node_ref(self.db, self.module))?;
|
||||
let place = self.expect_place(&subject);
|
||||
|
||||
let class_type =
|
||||
@@ -1486,7 +1479,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let subject_node = subject.node_ref(self.db, self.module);
|
||||
let place = {
|
||||
let subject = place_expr(subject_node)?;
|
||||
let subject = PlaceExpr::try_from_expr(subject_node)?;
|
||||
self.expect_place(&subject)
|
||||
};
|
||||
let subject_ty =
|
||||
@@ -1638,7 +1631,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
if !is_typeddict_or_union_with_typeddicts(self.db, subscript_value_type) {
|
||||
return None;
|
||||
}
|
||||
let subscript_place_expr = place_expr(subscript_value_expr)?;
|
||||
let subscript_place_expr = PlaceExpr::try_from_expr(subscript_value_expr)?;
|
||||
let Type::StringLiteral(key_literal) = subscript_key_type else {
|
||||
return None;
|
||||
};
|
||||
@@ -1724,7 +1717,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let subscript_place_expr = place_expr(subscript_value_expr)?;
|
||||
let subscript_place_expr = PlaceExpr::try_from_expr(subscript_value_expr)?;
|
||||
|
||||
// Skip narrowing if any tuple in the union has an out-of-bounds index.
|
||||
// A diagnostic will be emitted elsewhere for the out-of-bounds access.
|
||||
@@ -1741,8 +1734,8 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
// Filter the union based on whether each tuple element at the index could match the rhs.
|
||||
let filtered: Vec<_> = union
|
||||
.elements(self.db)
|
||||
let union_elements = union.elements(self.db);
|
||||
let filtered: Vec<_> = union_elements
|
||||
.iter()
|
||||
.filter(|elem| {
|
||||
elem.as_nominal_instance()
|
||||
@@ -1762,11 +1755,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
.collect();
|
||||
|
||||
// Only create a constraint if we actually narrowed something.
|
||||
if filtered.len() < union.elements(self.db).len() {
|
||||
if filtered.len() < union_elements.len() {
|
||||
let place = self.expect_place(&subscript_place_expr);
|
||||
Some((
|
||||
place,
|
||||
NarrowingConstraint::regular(UnionType::from_elements(self.db, filtered)),
|
||||
NarrowingConstraint::typeguard(UnionType::from_elements(self.db, filtered)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -2204,6 +2204,15 @@ pub(crate) struct Parameter<'db> {
|
||||
/// type semantics of the parameter.
|
||||
pub(crate) inferred_annotation: bool,
|
||||
|
||||
/// Variadic parameters can have starred annotations, e.g.
|
||||
/// - `*args: *Ts`
|
||||
/// - `*args: *tuple[int, ...]`
|
||||
/// - `*args: *tuple[int, *tuple[str, ...], bytes]`
|
||||
///
|
||||
/// The `*` prior to the type gives the annotation a different meaning,
|
||||
/// so this must be propagated upwards.
|
||||
has_starred_annotation: bool,
|
||||
|
||||
kind: ParameterKind<'db>,
|
||||
pub(crate) form: ParameterForm,
|
||||
}
|
||||
@@ -2213,6 +2222,7 @@ impl<'db> Parameter<'db> {
|
||||
Self {
|
||||
annotated_type: Type::unknown(),
|
||||
inferred_annotation: true,
|
||||
has_starred_annotation: false,
|
||||
kind: ParameterKind::PositionalOnly {
|
||||
name,
|
||||
default_type: None,
|
||||
@@ -2225,6 +2235,7 @@ impl<'db> Parameter<'db> {
|
||||
Self {
|
||||
annotated_type: Type::unknown(),
|
||||
inferred_annotation: true,
|
||||
has_starred_annotation: false,
|
||||
kind: ParameterKind::PositionalOrKeyword {
|
||||
name,
|
||||
default_type: None,
|
||||
@@ -2237,6 +2248,7 @@ impl<'db> Parameter<'db> {
|
||||
Self {
|
||||
annotated_type: Type::unknown(),
|
||||
inferred_annotation: true,
|
||||
has_starred_annotation: false,
|
||||
kind: ParameterKind::Variadic { name },
|
||||
form: ParameterForm::Value,
|
||||
}
|
||||
@@ -2246,6 +2258,7 @@ impl<'db> Parameter<'db> {
|
||||
Self {
|
||||
annotated_type: Type::unknown(),
|
||||
inferred_annotation: true,
|
||||
has_starred_annotation: false,
|
||||
kind: ParameterKind::KeywordOnly {
|
||||
name,
|
||||
default_type: None,
|
||||
@@ -2258,6 +2271,7 @@ impl<'db> Parameter<'db> {
|
||||
Self {
|
||||
annotated_type: Type::unknown(),
|
||||
inferred_annotation: true,
|
||||
has_starred_annotation: false,
|
||||
kind: ParameterKind::KeywordVariadic { name },
|
||||
form: ParameterForm::Value,
|
||||
}
|
||||
@@ -2306,6 +2320,7 @@ impl<'db> Parameter<'db> {
|
||||
.kind
|
||||
.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
|
||||
inferred_annotation: self.inferred_annotation,
|
||||
has_starred_annotation: self.has_starred_annotation,
|
||||
form: self.form,
|
||||
}
|
||||
}
|
||||
@@ -2323,7 +2338,8 @@ impl<'db> Parameter<'db> {
|
||||
annotated_type,
|
||||
kind,
|
||||
form,
|
||||
..
|
||||
has_starred_annotation,
|
||||
inferred_annotation: _,
|
||||
} = self;
|
||||
|
||||
// Ensure unions and intersections are ordered in the annotated type.
|
||||
@@ -2364,6 +2380,7 @@ impl<'db> Parameter<'db> {
|
||||
// Normalize `inferred_annotation` to `false` since it's a display-only field
|
||||
// that doesn't affect type semantics.
|
||||
inferred_annotation: false,
|
||||
has_starred_annotation: *has_starred_annotation,
|
||||
kind,
|
||||
form: *form,
|
||||
}
|
||||
@@ -2377,6 +2394,7 @@ impl<'db> Parameter<'db> {
|
||||
) -> Option<Self> {
|
||||
let Parameter {
|
||||
annotated_type,
|
||||
has_starred_annotation,
|
||||
inferred_annotation,
|
||||
kind,
|
||||
form,
|
||||
@@ -2437,6 +2455,7 @@ impl<'db> Parameter<'db> {
|
||||
Some(Self {
|
||||
annotated_type,
|
||||
inferred_annotation: *inferred_annotation,
|
||||
has_starred_annotation: *has_starred_annotation,
|
||||
kind,
|
||||
form: *form,
|
||||
})
|
||||
@@ -2448,18 +2467,20 @@ impl<'db> Parameter<'db> {
|
||||
parameter: &ast::Parameter,
|
||||
kind: ParameterKind<'db>,
|
||||
) -> Self {
|
||||
let (annotated_type, inferred_annotation) = if let Some(annotation) = parameter.annotation()
|
||||
{
|
||||
(
|
||||
function_signature_expression_type(db, definition, annotation),
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
(Type::unknown(), true)
|
||||
};
|
||||
let (annotated_type, inferred_annotation, has_starred_annotation) =
|
||||
if let Some(annotation) = parameter.annotation() {
|
||||
(
|
||||
function_signature_expression_type(db, definition, annotation),
|
||||
false,
|
||||
annotation.is_starred_expr(),
|
||||
)
|
||||
} else {
|
||||
(Type::unknown(), true, false)
|
||||
};
|
||||
Self {
|
||||
annotated_type,
|
||||
kind,
|
||||
has_starred_annotation,
|
||||
form: ParameterForm::Value,
|
||||
inferred_annotation,
|
||||
}
|
||||
@@ -2511,6 +2532,12 @@ impl<'db> Parameter<'db> {
|
||||
self.annotated_type
|
||||
}
|
||||
|
||||
/// Return `true` if this parameter has a starred annotation,
|
||||
/// e.g. `*args: *Ts` or `*args: *tuple[int, *tuple[str, ...], bytes]`
|
||||
pub(crate) fn has_starred_annotation(&self) -> bool {
|
||||
self.has_starred_annotation
|
||||
}
|
||||
|
||||
/// Kind of the parameter.
|
||||
pub(crate) fn kind(&self) -> &ParameterKind<'db> {
|
||||
&self.kind
|
||||
|
||||
607
scripts/conformance.py
Normal file
607
scripts/conformance.py
Normal file
@@ -0,0 +1,607 @@
|
||||
"""
|
||||
Run typing conformance tests and compare results between two ty versions.
|
||||
|
||||
By default, this script will use `uv` to run the latest version of ty
|
||||
as the new version with `uvx ty@latest`. This requires `uv` to be installed
|
||||
and available in the system PATH.
|
||||
|
||||
If CONFORMANCE_SUITE_COMMIT is set, the hash will be used to create
|
||||
links to the corresponding line in the conformance repository for each
|
||||
diagnostic. Otherwise, it will default to `main'.
|
||||
|
||||
Examples:
|
||||
# Compare two specific ty versions
|
||||
%(prog)s --old-ty uvx ty@0.0.1a35 --new-ty uvx ty@0.0.7
|
||||
|
||||
# Use local ty builds
|
||||
%(prog)s --old-ty ./target/debug/ty-old --new-ty ./target/debug/ty-new
|
||||
|
||||
# Custom test directory
|
||||
%(prog)s --target-path custom/tests --old-ty uvx ty@0.0.1a35 --new-ty uvx ty@0.0.7
|
||||
|
||||
# Show all diagnostics (not just changed ones)
|
||||
%(prog)s --all --old-ty uvx ty@0.0.1a35 --new-ty uvx ty@0.0.7
|
||||
|
||||
# Show a diff with local paths to the test directory instead of table of links
|
||||
%(prog)s --old-ty uvx ty@0.0.1a35 --new-ty uvx ty@0.0.7 --format diff
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from enum import Flag, StrEnum, auto
|
||||
from functools import reduce
|
||||
from itertools import groupby
|
||||
from operator import attrgetter, or_
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
# The conformance tests include 4 types of errors:
|
||||
# 1. Required errors (E): The type checker must raise an error on this line
|
||||
# 2. Optional errors (E?): The type checker may raise an error on this line
|
||||
# 3. Tagged errors (E[tag]): The type checker must raise at most one error on any of the lines in a file with matching tags
|
||||
# 4. Tagged multi-errors (E[tag+]): The type checker should raise one or more errors on any of the tagged lines
|
||||
# This regex pattern parses the error lines in the conformance tests, but the following
|
||||
# implementation treats all errors as required errors.
|
||||
CONFORMANCE_ERROR_PATTERN = re.compile(
|
||||
r"""
|
||||
\#\s*E # "# E" begins each error
|
||||
(?P<optional>\?)? # Optional '?' (E?) indicates that an error is optional
|
||||
(?: # An optional tag for errors that may appear on multiple lines at most once
|
||||
\[
|
||||
(?P<tag>[^+\]]+) # identifier
|
||||
(?P<multi>\+)? # '+' indicates that an error may occur more than once on tagged lines
|
||||
\]
|
||||
)?
|
||||
(?:
|
||||
\s*:\s*(?P<description>.*) # optional description
|
||||
)?
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
CONFORMANCE_URL = "https://github.com/python/typing/blob/{conformance_suite_commit}/conformance/tests/{filename}#L{line}"
|
||||
CONFORMANCE_SUITE_COMMIT = os.environ.get("CONFORMANCE_SUITE_COMMIT", "main")
|
||||
|
||||
|
||||
class Source(Flag):
|
||||
OLD = auto()
|
||||
NEW = auto()
|
||||
EXPECTED = auto()
|
||||
|
||||
|
||||
class Classification(StrEnum):
|
||||
TRUE_POSITIVE = auto()
|
||||
FALSE_POSITIVE = auto()
|
||||
TRUE_NEGATIVE = auto()
|
||||
FALSE_NEGATIVE = auto()
|
||||
|
||||
def into_title(self) -> str:
|
||||
match self:
|
||||
case Classification.TRUE_POSITIVE:
|
||||
return "True positives added 🎉"
|
||||
case Classification.FALSE_POSITIVE:
|
||||
return "False positives added 🫤"
|
||||
case Classification.TRUE_NEGATIVE:
|
||||
return "False positives removed 🎉"
|
||||
case Classification.FALSE_NEGATIVE:
|
||||
return "True positives removed 🫤"
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class Position:
|
||||
line: int
|
||||
column: int
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class Positions:
|
||||
begin: Position
|
||||
end: Position
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class Location:
|
||||
path: str
|
||||
positions: Positions
|
||||
|
||||
def as_link(self) -> str:
|
||||
file = os.path.basename(self.path)
|
||||
link = CONFORMANCE_URL.format(
|
||||
conformance_suite_commit=CONFORMANCE_SUITE_COMMIT,
|
||||
filename=file,
|
||||
line=self.positions.begin.line,
|
||||
)
|
||||
return f"[{file}:{self.positions.begin.line}:{self.positions.begin.column}]({link})"
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class Diagnostic:
|
||||
check_name: str
|
||||
description: str
|
||||
severity: str
|
||||
fingerprint: str | None
|
||||
location: Location
|
||||
source: Source
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"{self.location.path}:{self.location.positions.begin.line}:"
|
||||
f"{self.location.positions.begin.column}: "
|
||||
f"{self.severity_for_display}[{self.check_name}] {self.description}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_gitlab_output(
|
||||
cls,
|
||||
dct: dict[str, Any],
|
||||
source: Source,
|
||||
) -> Self:
|
||||
return cls(
|
||||
check_name=dct["check_name"],
|
||||
description=dct["description"],
|
||||
severity=dct["severity"],
|
||||
fingerprint=dct["fingerprint"],
|
||||
location=Location(
|
||||
path=dct["location"]["path"],
|
||||
positions=Positions(
|
||||
begin=Position(
|
||||
line=dct["location"]["positions"]["begin"]["line"],
|
||||
column=dct["location"]["positions"]["begin"]["column"],
|
||||
),
|
||||
end=Position(
|
||||
line=dct["location"]["positions"]["end"]["line"],
|
||||
column=dct["location"]["positions"]["end"]["column"],
|
||||
),
|
||||
),
|
||||
),
|
||||
source=source,
|
||||
)
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
"""Key to group diagnostics by path and beginning line."""
|
||||
return f"{self.location.path}:{self.location.positions.begin.line}"
|
||||
|
||||
@property
|
||||
def severity_for_display(self) -> str:
|
||||
return {
|
||||
"major": "error",
|
||||
"minor": "warning",
|
||||
}.get(self.severity, "unknown")
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class GroupedDiagnostics:
|
||||
key: str
|
||||
sources: Source
|
||||
old: Diagnostic | None
|
||||
new: Diagnostic | None
|
||||
expected: Diagnostic | None
|
||||
|
||||
@property
|
||||
def changed(self) -> bool:
|
||||
return (Source.OLD in self.sources or Source.NEW in self.sources) and not (
|
||||
Source.OLD in self.sources and Source.NEW in self.sources
|
||||
)
|
||||
|
||||
@property
|
||||
def classification(self) -> Classification:
|
||||
if Source.NEW in self.sources and Source.EXPECTED in self.sources:
|
||||
return Classification.TRUE_POSITIVE
|
||||
elif Source.NEW in self.sources and Source.EXPECTED not in self.sources:
|
||||
return Classification.FALSE_POSITIVE
|
||||
elif Source.EXPECTED in self.sources:
|
||||
return Classification.FALSE_NEGATIVE
|
||||
else:
|
||||
return Classification.TRUE_NEGATIVE
|
||||
|
||||
def _render_row(self, diagnostic: Diagnostic):
|
||||
return f"| {diagnostic.location.as_link()} | {diagnostic.check_name} | {diagnostic.description} |"
|
||||
|
||||
def _render_diff(self, diagnostic: Diagnostic, *, removed: bool = False):
|
||||
sign = "-" if removed else "+"
|
||||
return f"{sign} {diagnostic}"
|
||||
|
||||
def display(self, format: Literal["diff", "github"]) -> str:
|
||||
match self.classification:
|
||||
case Classification.TRUE_POSITIVE | Classification.FALSE_POSITIVE:
|
||||
assert self.new is not None
|
||||
return (
|
||||
self._render_diff(self.new)
|
||||
if format == "diff"
|
||||
else self._render_row(self.new)
|
||||
)
|
||||
|
||||
case Classification.FALSE_NEGATIVE | Classification.TRUE_NEGATIVE:
|
||||
diagnostic = self.old or self.expected
|
||||
assert diagnostic is not None
|
||||
return (
|
||||
self._render_diff(diagnostic, removed=True)
|
||||
if format == "diff"
|
||||
else self._render_row(diagnostic)
|
||||
)
|
||||
|
||||
case _:
|
||||
raise ValueError(f"Unexpected classification: {self.classification}")
|
||||
|
||||
|
||||
@dataclass(kw_only=True, slots=True)
|
||||
class Statistics:
|
||||
true_positives: int = 0
|
||||
false_positives: int = 0
|
||||
false_negatives: int = 0
|
||||
|
||||
@property
|
||||
def precision(self) -> float:
|
||||
if self.true_positives + self.false_positives > 0:
|
||||
return self.true_positives / (self.true_positives + self.false_positives)
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def recall(self) -> float:
|
||||
if self.true_positives + self.false_negatives > 0:
|
||||
return self.true_positives / (self.true_positives + self.false_negatives)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
return self.true_positives + self.false_positives
|
||||
|
||||
|
||||
def collect_expected_diagnostics(path: Path) -> list[Diagnostic]:
|
||||
diagnostics: list[Diagnostic] = []
|
||||
for file in path.resolve().rglob("*.py"):
|
||||
for idx, line in enumerate(file.read_text().splitlines(), 1):
|
||||
if error := re.search(CONFORMANCE_ERROR_PATTERN, line):
|
||||
diagnostics.append(
|
||||
Diagnostic(
|
||||
check_name="conformance",
|
||||
description=error.group("description")
|
||||
or error.group("tag")
|
||||
or "Missing",
|
||||
severity="major",
|
||||
fingerprint=None,
|
||||
location=Location(
|
||||
path=file.as_posix(),
|
||||
positions=Positions(
|
||||
begin=Position(
|
||||
line=idx,
|
||||
column=error.start(),
|
||||
),
|
||||
end=Position(
|
||||
line=idx,
|
||||
column=error.end(),
|
||||
),
|
||||
),
|
||||
),
|
||||
source=Source.EXPECTED,
|
||||
)
|
||||
)
|
||||
|
||||
assert diagnostics, "Failed to discover any expected diagnostics!"
|
||||
return diagnostics
|
||||
|
||||
|
||||
def collect_ty_diagnostics(
|
||||
ty_path: list[str],
|
||||
source: Source,
|
||||
tests_path: str = ".",
|
||||
python_version: str = "3.12",
|
||||
) -> list[Diagnostic]:
|
||||
process = subprocess.run(
|
||||
[
|
||||
*ty_path,
|
||||
"check",
|
||||
f"--python-version={python_version}",
|
||||
"--output-format=gitlab",
|
||||
"--exit-zero",
|
||||
tests_path,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
if process.returncode != 0:
|
||||
print(process.stderr)
|
||||
raise RuntimeError(f"ty check failed with exit code {process.returncode}")
|
||||
|
||||
return [
|
||||
Diagnostic.from_gitlab_output(dct, source=source)
|
||||
for dct in json.loads(process.stdout)
|
||||
]
|
||||
|
||||
|
||||
def group_diagnostics_by_key(
|
||||
old: list[Diagnostic], new: list[Diagnostic], expected: list[Diagnostic]
|
||||
) -> list[GroupedDiagnostics]:
|
||||
diagnostics = [
|
||||
*old,
|
||||
*new,
|
||||
*expected,
|
||||
]
|
||||
sorted_diagnostics = sorted(diagnostics, key=attrgetter("key"))
|
||||
|
||||
grouped = []
|
||||
for key, group in groupby(sorted_diagnostics, key=attrgetter("key")):
|
||||
group = list(group)
|
||||
sources: Source = reduce(or_, (diag.source for diag in group))
|
||||
grouped.append(
|
||||
GroupedDiagnostics(
|
||||
key=key,
|
||||
sources=sources,
|
||||
old=next(filter(lambda diag: diag.source == Source.OLD, group), None),
|
||||
new=next(filter(lambda diag: diag.source == Source.NEW, group), None),
|
||||
expected=next(
|
||||
filter(lambda diag: diag.source == Source.EXPECTED, group), None
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return grouped
|
||||
|
||||
|
||||
def compute_stats(
|
||||
grouped_diagnostics: list[GroupedDiagnostics], source: Source
|
||||
) -> Statistics:
|
||||
if source == source.EXPECTED:
|
||||
# ty currently raises a false positive here due to incomplete enum.Flag support
|
||||
# see https://github.com/astral-sh/ty/issues/876
|
||||
num_errors = sum(
|
||||
1
|
||||
for g in grouped_diagnostics
|
||||
if source.EXPECTED in g.sources # ty:ignore[unsupported-operator]
|
||||
)
|
||||
return Statistics(
|
||||
true_positives=num_errors, false_positives=0, false_negatives=0
|
||||
)
|
||||
|
||||
def increment(statistics: Statistics, grouped: GroupedDiagnostics) -> Statistics:
|
||||
if (source in grouped.sources) and (Source.EXPECTED in grouped.sources):
|
||||
statistics.true_positives += 1
|
||||
elif source in grouped.sources:
|
||||
statistics.false_positives += 1
|
||||
elif Source.EXPECTED in grouped.sources:
|
||||
statistics.false_negatives += 1
|
||||
return statistics
|
||||
|
||||
return reduce(increment, grouped_diagnostics, Statistics())
|
||||
|
||||
|
||||
def render_grouped_diagnostics(
|
||||
grouped: list[GroupedDiagnostics],
|
||||
*,
|
||||
changed_only: bool = True,
|
||||
format: Literal["diff", "github"] = "diff",
|
||||
) -> str:
|
||||
if changed_only:
|
||||
grouped = [diag for diag in grouped if diag.changed]
|
||||
|
||||
sorted_by_class = sorted(
|
||||
grouped,
|
||||
key=attrgetter("classification"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
match format:
|
||||
case "diff":
|
||||
header = ["```diff"]
|
||||
footer = "```"
|
||||
case "github":
|
||||
header = [
|
||||
"| Location | Name | Message |",
|
||||
"|----------|------|---------|",
|
||||
]
|
||||
footer = ""
|
||||
case _:
|
||||
raise ValueError("format must be one of 'diff' or 'github'")
|
||||
|
||||
lines = []
|
||||
for classification, group in groupby(
|
||||
sorted_by_class, key=attrgetter("classification")
|
||||
):
|
||||
group = list(group)
|
||||
|
||||
lines.append(f"## {classification.into_title()}")
|
||||
lines.extend(["", "<details>", ""])
|
||||
|
||||
lines.extend(header)
|
||||
|
||||
for diag in group:
|
||||
lines.append(diag.display(format=format))
|
||||
|
||||
lines.append(footer)
|
||||
lines.extend(["", "</details>", ""])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def diff_format(
|
||||
diff: float,
|
||||
*,
|
||||
greater_is_better: bool = True,
|
||||
neutral: bool = False,
|
||||
is_percentage: bool = False,
|
||||
):
|
||||
increased = diff > 0
|
||||
good = " (✅)" if not neutral else ""
|
||||
bad = " (❌)" if not neutral else ""
|
||||
up = "⏫"
|
||||
down = "⏬"
|
||||
|
||||
match (greater_is_better, increased):
|
||||
case (True, True):
|
||||
return f"{up}{good}"
|
||||
case (False, True):
|
||||
return f"{up}{bad}"
|
||||
case (True, False):
|
||||
return f"{down}{bad}"
|
||||
case (False, False):
|
||||
return f"{down}{good}"
|
||||
|
||||
|
||||
def render_summary(grouped_diagnostics: list[GroupedDiagnostics]):
|
||||
def format_metric(diff: float, old: float, new: float):
|
||||
if diff > 0:
|
||||
return f"increased from {old:.2%} to {new:.2%}"
|
||||
if diff < 0:
|
||||
return f"decreased from {old:.2%} to {new:.2%}"
|
||||
return f"held steady at {old:.2%}"
|
||||
|
||||
old = compute_stats(grouped_diagnostics, source=Source.OLD)
|
||||
new = compute_stats(grouped_diagnostics, source=Source.NEW)
|
||||
|
||||
precision_change = new.precision - old.precision
|
||||
recall_change = new.recall - old.recall
|
||||
true_pos_change = new.true_positives - old.true_positives
|
||||
false_pos_change = new.false_positives - old.false_positives
|
||||
false_neg_change = new.false_negatives - old.false_negatives
|
||||
total_change = new.total - old.total
|
||||
|
||||
true_pos_diff = diff_format(true_pos_change, greater_is_better=True)
|
||||
false_pos_diff = diff_format(false_pos_change, greater_is_better=False)
|
||||
false_neg_diff = diff_format(false_neg_change, greater_is_better=False)
|
||||
precision_diff = diff_format(
|
||||
precision_change, greater_is_better=True, is_percentage=True
|
||||
)
|
||||
recall_diff = diff_format(recall_change, greater_is_better=True, is_percentage=True)
|
||||
total_diff = diff_format(total_change, neutral=True)
|
||||
|
||||
table = dedent(
|
||||
f"""
|
||||
## Typing Conformance
|
||||
|
||||
### Summary
|
||||
|
||||
| Metric | Old | New | Diff | Outcome |
|
||||
|--------|-----|-----|------|---------|
|
||||
| True Positives | {old.true_positives} | {new.true_positives} | {true_pos_change:+} | {true_pos_diff} |
|
||||
| False Positives | {old.false_positives} | {new.false_positives} | {false_pos_change:+} | {false_pos_diff} |
|
||||
| False Negatives | {old.false_negatives} | {new.false_negatives} | {false_neg_change:+} | {false_neg_diff} |
|
||||
| Total Diagnostics | {old.total} | {new.total} | {total_change} | {total_diff} |
|
||||
| Precision | {old.precision:.2%} | {new.precision:.2%} | {precision_change:+.2%} | {precision_diff} |
|
||||
| Recall | {old.recall:.2%} | {new.recall:.2%} | {recall_change:+.2%} | {recall_diff} |
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
summary = (
|
||||
f"The percentage of diagnostics emitted that were expected errors"
|
||||
f" {format_metric(precision_change, old.precision, new.precision)},"
|
||||
" and the percentage of expected errors that received a diagnostic"
|
||||
f" {format_metric(recall_change, old.recall, new.recall)}."
|
||||
)
|
||||
|
||||
return "\n".join([table, summary])
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--old-ty",
|
||||
nargs="+",
|
||||
help="Command to run old version of ty (default: uvx ty@0.0.1a35)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--new-ty",
|
||||
nargs="+",
|
||||
default=["uvx", "ty@latest"],
|
||||
help="Command to run new version of ty (default: uvx ty@0.0.7)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--tests-path",
|
||||
type=Path,
|
||||
default=Path("typing/conformance/tests"),
|
||||
help="Path to conformance tests directory (default: typing/conformance/tests)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--python-version",
|
||||
type=str,
|
||||
default="3.12",
|
||||
help="Python version to assume when running ty (default: 3.12)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="Show all diagnostics, not just changed ones",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--format", type=str, choices=["diff", "github"], default="github"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
help="Write output to file instead of stdout",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.old_ty is None:
|
||||
raise ValueError("old_ty is required")
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
expected = collect_expected_diagnostics(args.tests_path)
|
||||
|
||||
old = collect_ty_diagnostics(
|
||||
ty_path=args.old_ty,
|
||||
tests_path=str(args.tests_path),
|
||||
source=Source.OLD,
|
||||
python_version=args.python_version,
|
||||
)
|
||||
|
||||
new = collect_ty_diagnostics(
|
||||
ty_path=args.new_ty,
|
||||
tests_path=str(args.tests_path),
|
||||
source=Source.NEW,
|
||||
python_version=args.python_version,
|
||||
)
|
||||
|
||||
grouped = group_diagnostics_by_key(
|
||||
old=old,
|
||||
new=new,
|
||||
expected=expected,
|
||||
)
|
||||
|
||||
rendered = "\n\n".join(
|
||||
[
|
||||
render_summary(grouped),
|
||||
render_grouped_diagnostics(
|
||||
grouped, changed_only=not args.all, format=args.format
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
if args.output:
|
||||
args.output.write_text(rendered, encoding="utf-8")
|
||||
print(f"Output written to {args.output}", file=sys.stderr)
|
||||
else:
|
||||
print(rendered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
ty.schema.json
generated
10
ty.schema.json
generated
@@ -640,6 +640,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"invalid-generic-enum": {
|
||||
"title": "detects generic enum classes",
|
||||
"description": "## What it does\nChecks for enum classes that are also generic.\n\n## Why is this bad?\nEnum classes cannot be generic. Python does not support generic enums:\nattempting to create one will either result in an immediate `TypeError`\nat runtime, or will create a class that cannot be specialized in the way\nthat a normal generic class can.\n\n## Examples\n```python\nfrom enum import Enum\nfrom typing import Generic, TypeVar\n\nT = TypeVar(\"T\")\n\n# error: enum class cannot be generic (class creation fails with `TypeError`)\nclass E[T](Enum):\n A = 1\n\n# error: enum class cannot be generic (class creation fails with `TypeError`)\nclass F(Enum, Generic[T]):\n A = 1\n\n# error: enum class cannot be generic -- the class creation does not immediately fail...\nclass G(Generic[T], Enum):\n A = 1\n\n# ...but this raises `KeyError`:\nx: G[int]\n```\n\n## References\n- [Python documentation: Enum](https://docs.python.org/3/library/enum.html)",
|
||||
"default": "error",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invalid-ignore-comment": {
|
||||
"title": "detects ignore comments that use invalid syntax",
|
||||
"description": "## What it does\nChecks for `type: ignore` and `ty: ignore` comments that are syntactically incorrect.\n\n## Why is this bad?\nA syntactically incorrect ignore comment is probably a mistake and is useless.\n\n## Examples\n```py\na = 20 / 0 # type: ignoree\n```\n\nUse instead:\n\n```py\na = 20 / 0 # type: ignore\n```",
|
||||
|
||||
Reference in New Issue
Block a user