mirror of
https://github.com/astral-sh/ruff
synced 2026-01-20 21:10:48 -05:00
[ty] Add support for dynamic type() classes (#22291)
## Summary
This PR adds support for dynamic classes created via `type()`. The core
of the change is that `ClassLiteral` is now an enum:
```rust
pub enum ClassLiteral<'db> {
/// A class defined via a `class` statement.
Stmt(StmtClassLiteral<'db>),
/// A class created via the functional form `type(name, bases, dict)`.
Functional(FunctionalClassLiteral<'db>),
}
```
And, in turn, various methods on `ClassLiteral` like `body_scope` now
return `Option` or similar (and callers must adjust to that change in
signature).
Over time, we can expand the enum to include functional namedtuples,
etc. (I already have this working in a separate branch, and I believe it
slots in well.)
(I'd love help with the names -- I think `StmtClassLiteral` is kind of
lame. Maybe `DeclarativeClassLiteral`?)
Closes https://github.com/astral-sh/ty/issues/740.
---------
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -6,3 +6,4 @@
|
||||
possibly-unresolved-reference = "warn"
|
||||
possibly-missing-import = "warn"
|
||||
division-by-zero = "warn"
|
||||
unsupported-dynamic-base = "warn"
|
||||
|
||||
197
crates/ty/docs/rules.md
generated
197
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#L540" target="_blank">View source</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>
|
||||
</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#L139" target="_blank">View source</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>
|
||||
</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#L157" target="_blank">View source</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>
|
||||
</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#L208" target="_blank">View source</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>
|
||||
</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#L234" target="_blank">View source</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>
|
||||
</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#L259" target="_blank">View source</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>
|
||||
</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#L285" target="_blank">View source</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>
|
||||
</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#L311" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -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#L355" target="_blank">View source</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>
|
||||
</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#L333" target="_blank">View source</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>
|
||||
</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#L376" target="_blank">View source</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>
|
||||
</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#L397" target="_blank">View source</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>
|
||||
</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#L623" target="_blank">View source</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>
|
||||
</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#L647" target="_blank">View source</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>
|
||||
</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#L429" target="_blank">View source</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>
|
||||
</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#L701" target="_blank">View source</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>
|
||||
</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#L741" target="_blank">View source</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>
|
||||
</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#L2044" 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>
|
||||
|
||||
|
||||
@@ -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#L763" target="_blank">View source</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>
|
||||
</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#L793" target="_blank">View source</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>
|
||||
</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#L844" target="_blank">View source</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>
|
||||
</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#L865" target="_blank">View source</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>
|
||||
</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#L888" target="_blank">View source</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>
|
||||
</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#L1714" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -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#L2295" target="_blank">View source</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>
|
||||
</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#L924" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1077,7 +1077,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#L668" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1116,7 +1116,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#L955" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1151,7 +1151,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#L1052" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1185,7 +1185,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#L2197" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1292,7 +1292,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#L575" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1346,7 +1346,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#L1028" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1376,7 +1376,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#L1079" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1426,7 +1426,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#L1178" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1452,7 +1452,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#L983" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1483,7 +1483,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#L511" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1517,7 +1517,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#L1198" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1566,7 +1566,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#L722" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1591,7 +1591,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#L1241" 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>
|
||||
|
||||
|
||||
@@ -1687,7 +1687,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#L2333" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1733,7 +1733,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#L1007" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1760,7 +1760,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#L1473" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1807,7 +1807,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#L1280" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1837,7 +1837,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#L1304" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1867,7 +1867,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#L1356" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1901,7 +1901,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#L1328" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1935,7 +1935,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#L1384" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1970,7 +1970,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#L2172" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2001,7 +2001,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#L1413" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2026,7 +2026,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#L2145" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2059,7 +2059,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#L1432" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2088,7 +2088,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#L1514" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2114,7 +2114,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#L1455" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2138,7 +2138,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#L1687" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2171,7 +2171,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#L1565" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2198,7 +2198,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#L1898" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2225,7 +2225,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#L1586" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2253,7 +2253,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#L182" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2285,7 +2285,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#L1608" 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>
|
||||
|
||||
|
||||
@@ -2322,7 +2322,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#L1638" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2386,7 +2386,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#L2072" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2413,7 +2413,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#L2020" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2443,7 +2443,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#L1664" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2472,7 +2472,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#L1832" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2506,7 +2506,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#L1772" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2533,7 +2533,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#L1750" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2561,7 +2561,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#L1793" 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>
|
||||
|
||||
|
||||
@@ -2607,7 +2607,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#L1859" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2631,7 +2631,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#L1877" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2658,7 +2658,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#L1919" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2686,7 +2686,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#L2093" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2744,7 +2744,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#L1941" 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>
|
||||
|
||||
|
||||
@@ -2769,7 +2769,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#L1960" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2794,7 +2794,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#L811" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2833,7 +2833,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#L1534" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2864,13 +2864,54 @@ not b1 # exception raised here
|
||||
b1 < b2 < b1 # exception raised here
|
||||
```
|
||||
|
||||
## `unsupported-dynamic-base`
|
||||
|
||||
<small>
|
||||
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>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for dynamic class definitions (using `type()`) that have bases
|
||||
which are unsupported by ty.
|
||||
|
||||
This is equivalent to [`unsupported-base`] but applies to classes created
|
||||
via `type()` rather than `class` statements.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
If a dynamically created class has a base that is an unsupported type
|
||||
such as `type[T]`, ty will not be able to resolve the
|
||||
[method resolution order] (MRO) for the class. This may lead to an inferior
|
||||
understanding of your codebase and unpredictable type-checking behavior.
|
||||
|
||||
**Default level**
|
||||
|
||||
This rule is disabled by default because it will not cause a runtime error,
|
||||
and may be noisy on codebases that use `type()` in highly dynamic ways.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
def factory(base: type[Base]) -> type:
|
||||
# `base` has type `type[Base]`, not `type[Base]` itself
|
||||
return type("Dynamic", (base,), {}) # error: [unsupported-dynamic-base]
|
||||
```
|
||||
|
||||
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
[`unsupported-base`]: https://docs.astral.sh/ty/rules/unsupported-base
|
||||
|
||||
## `unsupported-operator`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.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#L1979" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2934,7 +2975,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#L1122" 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>
|
||||
|
||||
|
||||
@@ -2997,7 +3038,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#L2001" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -231,7 +231,8 @@ impl<'db> Definitions<'db> {
|
||||
ty_python_semantic::types::TypeDefinition::Module(module) => {
|
||||
ResolvedDefinition::Module(module.file(db)?)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::Class(definition)
|
||||
ty_python_semantic::types::TypeDefinition::StaticClass(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::DynamicClass(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::Function(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeVar(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
|
||||
|
||||
@@ -13,54 +13,6 @@ bool(1, 2)
|
||||
bool(NotBool())
|
||||
```
|
||||
|
||||
## Calls to `type()`
|
||||
|
||||
A single-argument call to `type()` returns an object that has the argument's meta-type. (This is
|
||||
tested more extensively in `crates/ty_python_semantic/resources/mdtest/attributes.md`, alongside the
|
||||
tests for the `__class__` attribute.)
|
||||
|
||||
```py
|
||||
reveal_type(type(1)) # revealed: <class 'int'>
|
||||
```
|
||||
|
||||
But a three-argument call to type creates a dynamic instance of the `type` class:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
reveal_type(type("Foo", (), {})) # revealed: type
|
||||
|
||||
reveal_type(type("Foo", (Base,), {"attr": 1})) # revealed: type
|
||||
```
|
||||
|
||||
Other numbers of arguments are invalid
|
||||
|
||||
```py
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", ())
|
||||
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
type("Foo", (), {}, weird_other_arg=42)
|
||||
```
|
||||
|
||||
The following calls are also invalid, due to incorrect argument types:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
|
||||
type(b"Foo", (), {})
|
||||
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
type("Foo", Base, {})
|
||||
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
## Calls to `str()`
|
||||
|
||||
### Valid calls
|
||||
|
||||
815
crates/ty_python_semantic/resources/mdtest/call/type.md
Normal file
815
crates/ty_python_semantic/resources/mdtest/call/type.md
Normal file
@@ -0,0 +1,815 @@
|
||||
# Calls to `type()`
|
||||
|
||||
## Single-argument form
|
||||
|
||||
A single-argument call to `type()` returns an object that has the argument's meta-type. (This is
|
||||
tested more extensively in `crates/ty_python_semantic/resources/mdtest/attributes.md`, alongside the
|
||||
tests for the `__class__` attribute.)
|
||||
|
||||
```py
|
||||
reveal_type(type(1)) # revealed: <class 'int'>
|
||||
```
|
||||
|
||||
## Three-argument form (dynamic class creation)
|
||||
|
||||
A three-argument call to `type()` creates a new class. We synthesize a class type using the name
|
||||
from the first argument:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
class Mixin: ...
|
||||
|
||||
# We synthesize a class type using the name argument
|
||||
Foo = type("Foo", (), {})
|
||||
reveal_type(Foo) # revealed: <class 'Foo'>
|
||||
|
||||
# With a single base class
|
||||
Foo2 = type("Foo", (Base,), {"attr": 1})
|
||||
reveal_type(Foo2) # revealed: <class 'Foo'>
|
||||
|
||||
# With multiple base classes
|
||||
Foo3 = type("Foo", (Base, Mixin), {})
|
||||
reveal_type(Foo3) # revealed: <class 'Foo'>
|
||||
|
||||
# The inferred type is assignable to type[Base] since Foo inherits from Base
|
||||
tests: list[type[Base]] = []
|
||||
testCaseClass = type("Foo", (Base,), {})
|
||||
tests.append(testCaseClass) # No error - type[Foo] is assignable to type[Base]
|
||||
```
|
||||
|
||||
The name can also be provided indirectly via a variable with a string literal type:
|
||||
|
||||
```py
|
||||
name = "IndirectClass"
|
||||
IndirectClass = type(name, (), {})
|
||||
reveal_type(IndirectClass) # revealed: <class 'IndirectClass'>
|
||||
|
||||
# Works with base classes too
|
||||
class Base: ...
|
||||
|
||||
base_name = "DerivedClass"
|
||||
DerivedClass = type(base_name, (Base,), {})
|
||||
reveal_type(DerivedClass) # revealed: <class 'DerivedClass'>
|
||||
```
|
||||
|
||||
## Distinct class types
|
||||
|
||||
Each `type()` call produces a distinct class type, even if they have the same name and bases:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
|
||||
class Base: ...
|
||||
|
||||
Foo1 = type("Foo", (Base,), {})
|
||||
Foo2 = type("Foo", (Base,), {})
|
||||
|
||||
# Even though they have the same name and bases, they are distinct types
|
||||
static_assert(not is_equivalent_to(Foo1, Foo2))
|
||||
|
||||
# Each instance is typed with its respective class
|
||||
foo1 = Foo1()
|
||||
foo2 = Foo2()
|
||||
|
||||
def takes_foo1(x: Foo1) -> None: ...
|
||||
def takes_foo2(x: Foo2) -> None: ...
|
||||
|
||||
takes_foo1(foo1) # OK
|
||||
takes_foo2(foo2) # OK
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo1` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`"
|
||||
takes_foo1(foo2)
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo2` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`"
|
||||
takes_foo2(foo1)
|
||||
```
|
||||
|
||||
## Instances and attribute access
|
||||
|
||||
Instances of dynamic classes are typed with the synthesized class name. Attributes from all base
|
||||
classes are accessible:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
def base_method(self) -> str:
|
||||
return "hello"
|
||||
|
||||
class Mixin:
|
||||
mixin_attr: str = "mixin"
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# Instance is typed with the synthesized class name
|
||||
reveal_type(foo) # revealed: Foo
|
||||
|
||||
# Inherited attributes are accessible
|
||||
reveal_type(foo.base_attr) # revealed: int
|
||||
reveal_type(foo.base_method()) # revealed: str
|
||||
|
||||
# Multiple inheritance: attributes from all bases are accessible
|
||||
Bar = type("Bar", (Base, Mixin), {})
|
||||
bar = Bar()
|
||||
reveal_type(bar.base_attr) # revealed: int
|
||||
reveal_type(bar.mixin_attr) # revealed: str
|
||||
```
|
||||
|
||||
Attributes from the namespace dict (third argument) are not tracked. Like Pyright, we error when
|
||||
attempting to access them:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {"custom_attr": 42})
|
||||
foo = Foo()
|
||||
|
||||
# error: [unresolved-attribute] "Object of type `Foo` has no attribute `custom_attr`"
|
||||
reveal_type(foo.custom_attr) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inheritance from dynamic classes
|
||||
|
||||
Regular classes can inherit from dynamic classes:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
DynamicClass = type("DynamicClass", (Base,), {})
|
||||
|
||||
class Child(DynamicClass):
|
||||
child_attr: str = "child"
|
||||
|
||||
child = Child()
|
||||
|
||||
# Attributes from the dynamic class's base are accessible
|
||||
reveal_type(child.base_attr) # revealed: int
|
||||
|
||||
# The child class's own attributes are accessible
|
||||
reveal_type(child.child_attr) # revealed: str
|
||||
|
||||
# Child instances are subtypes of DynamicClass instances
|
||||
def takes_dynamic(x: DynamicClass) -> None: ...
|
||||
|
||||
takes_dynamic(child) # No error - Child is a subtype of DynamicClass
|
||||
|
||||
# isinstance narrows to the dynamic class instance type
|
||||
def check_isinstance(x: object) -> None:
|
||||
if isinstance(x, DynamicClass):
|
||||
reveal_type(x) # revealed: DynamicClass
|
||||
|
||||
# Dynamic class inheriting from int narrows correctly with isinstance
|
||||
IntSubclass = type("IntSubclass", (int,), {})
|
||||
|
||||
def check_int_subclass(x: IntSubclass | str) -> None:
|
||||
if isinstance(x, int):
|
||||
# IntSubclass inherits from int, so it's included in the narrowed type
|
||||
reveal_type(x) # revealed: IntSubclass
|
||||
else:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
## Disjointness
|
||||
|
||||
Dynamic classes are not considered disjoint from unrelated types (since a subclass could inherit
|
||||
from both):
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
|
||||
def check_disjointness(x: Foo | int) -> None:
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: int
|
||||
else:
|
||||
# Foo and int are not considered disjoint because `class C(Foo, int)` could exist.
|
||||
reveal_type(x) # revealed: Foo & ~int
|
||||
```
|
||||
|
||||
Disjointness also works for `type[]` of dynamic classes:
|
||||
|
||||
```py
|
||||
from ty_extensions import is_disjoint_from, static_assert
|
||||
|
||||
# Dynamic classes with disjoint bases have disjoint type[] types.
|
||||
IntClass = type("IntClass", (int,), {})
|
||||
StrClass = type("StrClass", (str,), {})
|
||||
|
||||
static_assert(is_disjoint_from(type[IntClass], type[StrClass]))
|
||||
static_assert(is_disjoint_from(type[StrClass], type[IntClass]))
|
||||
|
||||
# Dynamic classes that share a common base are not disjoint.
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
Bar = type("Bar", (Base,), {})
|
||||
|
||||
static_assert(not is_disjoint_from(type[Foo], type[Bar]))
|
||||
```
|
||||
|
||||
## Using dynamic classes with `super()`
|
||||
|
||||
Dynamic classes can be used as the pivot class in `super()` calls:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
def method(self) -> int:
|
||||
return 42
|
||||
|
||||
DynamicChild = type("DynamicChild", (Base,), {})
|
||||
|
||||
# Using dynamic class as pivot with dynamic class instance owner
|
||||
fc = DynamicChild()
|
||||
reveal_type(super(DynamicChild, fc)) # revealed: <super: <class 'DynamicChild'>, DynamicChild>
|
||||
reveal_type(super(DynamicChild, fc).method()) # revealed: int
|
||||
|
||||
# Regular class inheriting from dynamic class
|
||||
class RegularChild(DynamicChild):
|
||||
pass
|
||||
|
||||
rc = RegularChild()
|
||||
reveal_type(super(RegularChild, rc)) # revealed: <super: <class 'RegularChild'>, RegularChild>
|
||||
reveal_type(super(RegularChild, rc).method()) # revealed: int
|
||||
|
||||
# Using dynamic class as pivot with regular class instance owner
|
||||
reveal_type(super(DynamicChild, rc)) # revealed: <super: <class 'DynamicChild'>, RegularChild>
|
||||
reveal_type(super(DynamicChild, rc).method()) # revealed: int
|
||||
```
|
||||
|
||||
## Dynamic class inheritance chains
|
||||
|
||||
Dynamic classes can inherit from other dynamic classes:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
# Create a dynamic class that inherits from a regular class.
|
||||
Parent = type("Parent", (Base,), {})
|
||||
reveal_type(Parent) # revealed: <class 'Parent'>
|
||||
|
||||
# Create a dynamic class that inherits from another dynamic class.
|
||||
ChildCls = type("ChildCls", (Parent,), {})
|
||||
reveal_type(ChildCls) # revealed: <class 'ChildCls'>
|
||||
|
||||
# Child instances have access to attributes from the entire inheritance chain.
|
||||
child = ChildCls()
|
||||
reveal_type(child) # revealed: ChildCls
|
||||
reveal_type(child.base_attr) # revealed: int
|
||||
|
||||
# Child instances are subtypes of `Parent` instances.
|
||||
def takes_parent(x: Parent) -> None: ...
|
||||
|
||||
takes_parent(child) # No error - `ChildCls` is a subtype of `Parent`
|
||||
```
|
||||
|
||||
## Dataclass transform inheritance
|
||||
|
||||
Dynamic classes that inherit from a `@dataclass_transform()` decorated base class are recognized as
|
||||
dataclass-like and have the synthesized `__dataclass_fields__` attribute:
|
||||
|
||||
```py
|
||||
from dataclasses import Field
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class DataclassBase:
|
||||
"""Base class decorated with @dataclass_transform()."""
|
||||
|
||||
pass
|
||||
|
||||
# A dynamic class inheriting from a dataclass_transform base
|
||||
DynamicModel = type("DynamicModel", (DataclassBase,), {})
|
||||
|
||||
# The dynamic class has __dataclass_fields__ synthesized
|
||||
reveal_type(DynamicModel.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
```
|
||||
|
||||
## Applying `@dataclass` decorator directly
|
||||
|
||||
Applying the `@dataclass` decorator directly to a dynamic class is supported:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
Foo = type("Foo", (), {})
|
||||
Foo = dataclass(Foo)
|
||||
|
||||
reveal_type(Foo.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
```
|
||||
|
||||
## Generic base classes
|
||||
|
||||
Dynamic classes with generic base classes:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
value: T
|
||||
|
||||
# Dynamic class inheriting from a generic class specialization
|
||||
IntContainer = type("IntContainer", (Container[int],), {})
|
||||
reveal_type(IntContainer) # revealed: <class 'IntContainer'>
|
||||
|
||||
container = IntContainer()
|
||||
reveal_type(container) # revealed: IntContainer
|
||||
reveal_type(container.value) # revealed: int
|
||||
```
|
||||
|
||||
## `type()` and `__class__` on dynamic instances
|
||||
|
||||
`type(instance)` returns the class of the dynamic instance:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# type() on an instance returns the class
|
||||
reveal_type(type(foo)) # revealed: type[Foo]
|
||||
```
|
||||
|
||||
`__class__` attribute access on dynamic instances:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# __class__ returns the class type
|
||||
reveal_type(foo.__class__) # revealed: type[Foo]
|
||||
```
|
||||
|
||||
`__class__` on the dynamic class itself returns the metaclass (consistent with static classes):
|
||||
|
||||
```py
|
||||
class StaticClass: ...
|
||||
|
||||
DynamicClass = type("DynamicClass", (), {})
|
||||
|
||||
# Both static and dynamic classes have `type` as their metaclass
|
||||
reveal_type(StaticClass.__class__) # revealed: <class 'type'>
|
||||
reveal_type(DynamicClass.__class__) # revealed: <class 'type'>
|
||||
```
|
||||
|
||||
## Subtype relationships
|
||||
|
||||
Dynamic instances are subtypes of `object`:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
# All dynamic instances are subtypes of object
|
||||
def takes_object(x: object) -> None: ...
|
||||
|
||||
takes_object(foo) # No error - Foo is a subtype of object
|
||||
|
||||
# Even dynamic classes with no explicit bases are subtypes of object
|
||||
EmptyBases = type("EmptyBases", (), {})
|
||||
empty = EmptyBases()
|
||||
takes_object(empty) # No error
|
||||
```
|
||||
|
||||
## Attributes from `builtins.type`
|
||||
|
||||
Attributes defined on `builtins.type` are accessible on dynamic classes:
|
||||
|
||||
```py
|
||||
T = type("T", (), {})
|
||||
|
||||
# Inherited from `builtins.type`:
|
||||
reveal_type(T.__dictoffset__) # revealed: int
|
||||
reveal_type(T.__name__) # revealed: str
|
||||
reveal_type(T.__bases__) # revealed: tuple[type, ...]
|
||||
reveal_type(T.__mro__) # revealed: tuple[type, ...]
|
||||
```
|
||||
|
||||
## Invalid calls
|
||||
|
||||
Other numbers of arguments are invalid:
|
||||
|
||||
```py
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Foo", ())) # revealed: Unknown
|
||||
|
||||
# TODO: the keyword arguments for `Foo`/`Bar`/`Baz` here are invalid
|
||||
# (you cannot pass `metaclass=` to `type()`, and none of them have
|
||||
# base classes with `__init_subclass__` methods),
|
||||
# but `type[Unknown]` would be better than `Unknown` here
|
||||
#
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Foo", (), {}, weird_other_arg=42)) # revealed: Unknown
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Bar", (int,), {}, weird_other_arg=42)) # revealed: Unknown
|
||||
# error: [no-matching-overload] "No overload of class `type` matches arguments"
|
||||
reveal_type(type("Baz", (), {}, metaclass=type)) # revealed: Unknown
|
||||
```
|
||||
|
||||
The following calls are also invalid, due to incorrect argument types.
|
||||
|
||||
Inline calls (not assigned to a variable) fall back to regular `type` overload matching, which
|
||||
produces slightly different error messages than assigned dynamic class creation:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
|
||||
type(b"Foo", (), {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
|
||||
type("Foo", Base, {})
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
|
||||
type("Foo", (1, 2), {})
|
||||
|
||||
# error: 22 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `dict[str, Any]`, found `dict[str | bytes, Any]`"
|
||||
type("Foo", (Base,), {b"attr": 1})
|
||||
```
|
||||
|
||||
## `type[...]` as base class
|
||||
|
||||
`type[...]` (SubclassOf) types cannot be used as base classes. When a `type[...]` is used in the
|
||||
bases tuple, we emit a diagnostic and insert `Unknown` into the MRO. This gives exactly one
|
||||
diagnostic about the unsupported base, rather than cascading errors:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
def f(x: type[Base]):
|
||||
# error: [unsupported-dynamic-base] "Unsupported class base"
|
||||
Child = type("Child", (x,), {})
|
||||
|
||||
# The class is still created with `Unknown` in MRO, allowing attribute access
|
||||
reveal_type(Child) # revealed: <class 'Child'>
|
||||
reveal_mro(Child) # revealed: (<class 'Child'>, Unknown, <class 'object'>)
|
||||
child = Child()
|
||||
reveal_type(child) # revealed: Child
|
||||
|
||||
# Attributes from `Unknown` are accessible without further errors
|
||||
reveal_type(child.base_attr) # revealed: Unknown
|
||||
```
|
||||
|
||||
## MRO errors
|
||||
|
||||
MRO errors are detected and reported:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
# Duplicate bases are detected
|
||||
# error: [duplicate-base] "Duplicate base class <class 'A'> in class `Dup`"
|
||||
Dup = type("Dup", (A, A), {})
|
||||
```
|
||||
|
||||
Unknown bases (from unresolved imports) don't trigger duplicate-base diagnostics, since we can't
|
||||
know if they represent the same type:
|
||||
|
||||
```py
|
||||
from unresolved_module import Bar, Baz # error: [unresolved-import]
|
||||
|
||||
# No duplicate-base error here - Bar and Baz are Unknown, and we can't
|
||||
# know if they're the same type.
|
||||
X = type("X", (Bar, Baz), {})
|
||||
```
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
class C(A): ...
|
||||
|
||||
# This creates an inconsistent MRO because D would need B before C (from first base)
|
||||
# but also C before B (from second base inheritance through A)
|
||||
class X(B, C): ...
|
||||
class Y(C, B): ...
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Conflict` with bases `[<class 'X'>, <class 'Y'>]`"
|
||||
Conflict = type("Conflict", (X, Y), {})
|
||||
```
|
||||
|
||||
## Metaclass conflicts
|
||||
|
||||
Metaclass conflicts are detected and reported:
|
||||
|
||||
```py
|
||||
class Meta1(type): ...
|
||||
class Meta2(type): ...
|
||||
class A(metaclass=Meta1): ...
|
||||
class B(metaclass=Meta2): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`Bad`) must be a subclass of the metaclasses of all its bases, but `Meta1` (metaclass of base class `<class 'A'>`) and `Meta2` (metaclass of base class `<class 'B'>`) have no subclass relationship"
|
||||
Bad = type("Bad", (A, B), {})
|
||||
```
|
||||
|
||||
## Cyclic dynamic class definitions
|
||||
|
||||
Self-referential class definitions using `type()` are detected. The name being defined is referenced
|
||||
in the bases tuple before it's available:
|
||||
|
||||
```pyi
|
||||
# error: [unresolved-reference] "Name `X` used when not defined"
|
||||
X = type("X", (X,), {})
|
||||
```
|
||||
|
||||
## Dynamic class names (non-literal strings)
|
||||
|
||||
When the class name is not a string literal, we still create a class literal type but with a
|
||||
placeholder name `<unknown>`:
|
||||
|
||||
```py
|
||||
def make_class(name: str):
|
||||
# When the name is a dynamic string, we use a placeholder name
|
||||
cls = type(name, (), {})
|
||||
reveal_type(cls) # revealed: <class '<unknown>'>
|
||||
return cls
|
||||
|
||||
def make_classes(name1: str, name2: str):
|
||||
cls1 = type(name1, (), {})
|
||||
cls2 = type(name2, (), {})
|
||||
|
||||
def inner(x: cls1): ...
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `inner` is incorrect: Expected `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:8`, found `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:9`"
|
||||
inner(cls2())
|
||||
```
|
||||
|
||||
When the name comes from a union of string literals, we also use a placeholder name:
|
||||
|
||||
```py
|
||||
import random
|
||||
|
||||
name = "Foo" if random.random() > 0.5 else "Bar"
|
||||
reveal_type(name) # revealed: Literal["Foo", "Bar"]
|
||||
|
||||
# We cannot determine which name will be used at runtime
|
||||
cls = type(name, (), {})
|
||||
reveal_type(cls) # revealed: <class '<unknown>'>
|
||||
```
|
||||
|
||||
## Dynamic bases (variable tuple)
|
||||
|
||||
When the bases tuple is a function parameter with a non-literal tuple type, we still create a class
|
||||
literal type but with `Unknown` in the MRO. This means instances are treated highly dynamically -
|
||||
any attribute access returns `Unknown`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Base1: ...
|
||||
class Base2: ...
|
||||
|
||||
def make_class(bases: tuple[type, ...]):
|
||||
# Class literal is created with Unknown base in MRO
|
||||
cls = type("Cls", bases, {})
|
||||
reveal_type(cls) # revealed: <class 'Cls'>
|
||||
reveal_mro(cls) # revealed: (<class 'Cls'>, Unknown, <class 'object'>)
|
||||
|
||||
# Instances have dynamic attribute access due to Unknown base
|
||||
instance = cls()
|
||||
reveal_type(instance) # revealed: Cls
|
||||
reveal_type(instance.any_attr) # revealed: Unknown
|
||||
reveal_type(instance.any_method()) # revealed: Unknown
|
||||
|
||||
return cls
|
||||
```
|
||||
|
||||
When `bases` is a module-level variable holding a tuple of class literals, we can extract the base
|
||||
classes:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
attr: int = 1
|
||||
|
||||
bases = (Base,)
|
||||
Cls = type("Cls", bases, {})
|
||||
reveal_type(Cls) # revealed: <class 'Cls'>
|
||||
|
||||
instance = Cls()
|
||||
reveal_type(instance.attr) # revealed: int
|
||||
```
|
||||
|
||||
## Variadic arguments
|
||||
|
||||
Unpacking arguments with `*args` or `**kwargs`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Base: ...
|
||||
|
||||
# Unpacking a tuple for bases
|
||||
bases_tuple = (Base,)
|
||||
Cls1 = type("Cls1", (*bases_tuple,), {})
|
||||
reveal_type(Cls1) # revealed: <class 'Cls1'>
|
||||
reveal_mro(Cls1) # revealed: (<class 'Cls1'>, @Todo(StarredExpression), <class 'object'>)
|
||||
|
||||
# Unpacking a dict for the namespace - the dict contents are not tracked anyway
|
||||
namespace = {"attr": 1}
|
||||
Cls2 = type("Cls2", (Base,), {**namespace})
|
||||
reveal_type(Cls2) # revealed: <class 'Cls2'>
|
||||
```
|
||||
|
||||
When `*args` or `**kwargs` fill an unknown number of parameters, we cannot determine which overload
|
||||
of `type()` is being called:
|
||||
|
||||
```py
|
||||
def f(*args, **kwargs):
|
||||
# Completely dynamic: could be 1-arg or 3-arg form
|
||||
A = type(*args, **kwargs)
|
||||
reveal_type(A) # revealed: type[Unknown]
|
||||
|
||||
# Has a string first arg, but unknown additional args from *args
|
||||
B = type("B", *args, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(B) # revealed: <class 'str'>
|
||||
|
||||
# Has string and tuple, but unknown additional args
|
||||
C = type("C", (), *args, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(C) # revealed: type
|
||||
|
||||
# All three positional args provided, only **kwargs unknown
|
||||
D = type("D", (), {}, **kwargs)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(D) # revealed: type
|
||||
|
||||
# Three starred expressions - we can't know how they expand
|
||||
a = ("E",)
|
||||
b = ((),)
|
||||
c = ({},)
|
||||
E = type(*a, *b, *c)
|
||||
# TODO: `type[Unknown]` would cause fewer false positives
|
||||
reveal_type(E) # revealed: type
|
||||
```
|
||||
|
||||
## Explicit type annotations
|
||||
|
||||
TODO: Annotated assignments with `type()` calls don't currently synthesize the specific class type.
|
||||
This will be fixed when we support all `type()` calls (including inline) via generic handling.
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
# TODO: Should infer `<class 'T'>` instead of `type`
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: type
|
||||
|
||||
# TODO: Should infer `<class 'Derived'>` instead of `type[Base]}
|
||||
# error: [invalid-assignment] "Object of type `type` is not assignable to `type[Base]`"
|
||||
Derived: type[Base] = type("Derived", (Base,), {})
|
||||
reveal_type(Derived) # revealed: type[Base]
|
||||
```
|
||||
|
||||
## Special base classes
|
||||
|
||||
Some special base classes work with dynamic class creation, but special semantics may not be fully
|
||||
synthesized:
|
||||
|
||||
### Protocol bases
|
||||
|
||||
```py
|
||||
# Protocol bases work - the class is created as a subclass of the protocol
|
||||
from typing import Protocol
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class MyProtocol(Protocol):
|
||||
def method(self) -> int: ...
|
||||
|
||||
ProtoImpl = type("ProtoImpl", (MyProtocol,), {})
|
||||
reveal_type(ProtoImpl) # revealed: <class 'ProtoImpl'>
|
||||
reveal_mro(ProtoImpl) # revealed: (<class 'ProtoImpl'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
|
||||
instance = ProtoImpl()
|
||||
reveal_type(instance) # revealed: ProtoImpl
|
||||
```
|
||||
|
||||
### TypedDict bases
|
||||
|
||||
```py
|
||||
# TypedDict bases work but TypedDict semantics aren't applied to dynamic subclasses
|
||||
from typing_extensions import TypedDict
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class MyDict(TypedDict):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
DictSubclass = type("DictSubclass", (MyDict,), {})
|
||||
reveal_type(DictSubclass) # revealed: <class 'DictSubclass'>
|
||||
reveal_mro(DictSubclass) # revealed: (<class 'DictSubclass'>, <class 'MyDict'>, typing.TypedDict, <class 'object'>)
|
||||
```
|
||||
|
||||
### NamedTuple bases
|
||||
|
||||
```py
|
||||
# NamedTuple bases work but the dynamic subclass isn't recognized as a NamedTuple
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
Point3D = type("Point3D", (Point,), {})
|
||||
reveal_type(Point3D) # revealed: <class 'Point3D'>
|
||||
# fmt: off
|
||||
reveal_mro(Point3D) # revealed: (<class 'Point3D'>, <class 'Point'>, <class 'tuple[int, int]'>, <class 'Sequence[int]'>, <class 'Reversible[int]'>, <class 'Collection[int]'>, <class 'Iterable[int]'>, <class 'Container[int]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
### Enum bases
|
||||
|
||||
```py
|
||||
# Enum subclassing via type() is not supported - EnumMeta requires special dict handling
|
||||
# that type() cannot provide. This applies even to empty enums.
|
||||
from enum import Enum
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
|
||||
class EmptyEnum(Enum):
|
||||
pass
|
||||
|
||||
# TODO: We should emit a diagnostic here - type() cannot create Enum subclasses
|
||||
ExtendedColor = type("ExtendedColor", (Color,), {})
|
||||
reveal_type(ExtendedColor) # revealed: <class 'ExtendedColor'>
|
||||
|
||||
# Even empty enums fail - EnumMeta requires special dict handling
|
||||
# TODO: We should emit a diagnostic here too
|
||||
ValidExtension = type("ValidExtension", (EmptyEnum,), {})
|
||||
reveal_type(ValidExtension) # revealed: <class 'ValidExtension'>
|
||||
```
|
||||
|
||||
## `__init_subclass__` keyword arguments
|
||||
|
||||
When a base class defines `__init_subclass__` with required arguments, those should be passed to
|
||||
`type()`. This is not yet supported:
|
||||
|
||||
```py
|
||||
class Base:
|
||||
def __init_subclass__(cls, required_arg: str, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.config = required_arg
|
||||
|
||||
# Regular class definition - this works and passes the argument
|
||||
class Child(Base, required_arg="value"):
|
||||
pass
|
||||
|
||||
# The dynamically assigned attribute has Unknown in its type
|
||||
reveal_type(Child.config) # revealed: Unknown | str
|
||||
|
||||
# Dynamic class creation - keyword arguments are not yet supported
|
||||
# TODO: This should work: type("DynamicChild", (Base,), {}, required_arg="value")
|
||||
# error: [no-matching-overload]
|
||||
DynamicChild = type("DynamicChild", (Base,), {}, required_arg="value")
|
||||
```
|
||||
|
||||
## Empty bases tuple
|
||||
|
||||
When the bases tuple is empty, the class implicitly inherits from `object`:
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
EmptyBases = type("EmptyBases", (), {})
|
||||
reveal_type(EmptyBases) # revealed: <class 'EmptyBases'>
|
||||
reveal_mro(EmptyBases) # revealed: (<class 'EmptyBases'>, <class 'object'>)
|
||||
|
||||
instance = EmptyBases()
|
||||
reveal_type(instance) # revealed: EmptyBases
|
||||
|
||||
# object methods are available
|
||||
reveal_type(instance.__hash__()) # revealed: int
|
||||
reveal_type(instance.__str__()) # revealed: str
|
||||
```
|
||||
|
||||
## Custom metaclass via bases
|
||||
|
||||
When a base class has a custom metaclass, the dynamic class inherits that metaclass:
|
||||
|
||||
```py
|
||||
class MyMeta(type):
|
||||
custom_attr: str = "meta"
|
||||
|
||||
class Base(metaclass=MyMeta): ...
|
||||
|
||||
# Dynamic class inherits the metaclass from Base
|
||||
Dynamic = type("Dynamic", (Base,), {})
|
||||
reveal_type(Dynamic) # revealed: <class 'Dynamic'>
|
||||
|
||||
# Metaclass attributes are accessible on the class
|
||||
reveal_type(Dynamic.custom_attr) # revealed: str
|
||||
```
|
||||
@@ -1686,3 +1686,38 @@ reveal_type(ordered_foo) # revealed: <class 'Foo'>
|
||||
reveal_type(ordered_foo()) # revealed: Foo
|
||||
reveal_type(ordered_foo() < ordered_foo()) # revealed: bool
|
||||
```
|
||||
|
||||
## Dynamic class literals
|
||||
|
||||
Dynamic classes created with `type()` can be wrapped with `dataclass()` as a function:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Basic dynamic class wrapped with dataclass
|
||||
DynamicFoo = type("DynamicFoo", (), {})
|
||||
DynamicFoo = dataclass(DynamicFoo)
|
||||
|
||||
# The class is recognized as a dataclass
|
||||
reveal_type(DynamicFoo.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
|
||||
# Can create instances
|
||||
instance = DynamicFoo()
|
||||
reveal_type(instance) # revealed: DynamicFoo
|
||||
```
|
||||
|
||||
Dynamic classes that inherit from a dataclass base also work:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Base:
|
||||
x: int
|
||||
|
||||
# Dynamic class inheriting from a dataclass
|
||||
DynamicChild = type("DynamicChild", (Base,), {})
|
||||
DynamicChild = dataclass(DynamicChild)
|
||||
|
||||
reveal_type(DynamicChild.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
```
|
||||
|
||||
@@ -870,6 +870,102 @@ static_assert(not has_member(F, "__match_args__"))
|
||||
static_assert(not has_member(F(), "__weakref__"))
|
||||
```
|
||||
|
||||
### Dynamic classes (created via `type()`)
|
||||
|
||||
Dynamic classes created using the three-argument form of `type()` support autocomplete for members
|
||||
inherited from their base classes on the class object:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
def base_method(self) -> str:
|
||||
return "hello"
|
||||
|
||||
class Mixin:
|
||||
mixin_attr: str = "mixin"
|
||||
|
||||
# Dynamic class with a single base
|
||||
DynamicSingle = type("DynamicSingle", (Base,), {})
|
||||
|
||||
# The class object has access to base class attributes
|
||||
static_assert(has_member(DynamicSingle, "base_attr"))
|
||||
static_assert(has_member(DynamicSingle, "base_method"))
|
||||
|
||||
# Dynamic class with multiple bases
|
||||
DynamicMulti = type("DynamicMulti", (Base, Mixin), {})
|
||||
|
||||
static_assert(has_member(DynamicMulti, "base_attr"))
|
||||
static_assert(has_member(DynamicMulti, "mixin_attr"))
|
||||
```
|
||||
|
||||
Members from `object` and the `type` metaclass are available on the class object:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
Dynamic = type("Dynamic", (), {})
|
||||
|
||||
# object members are available on the class
|
||||
static_assert(has_member(Dynamic, "__doc__"))
|
||||
static_assert(has_member(Dynamic, "__init__"))
|
||||
|
||||
# type metaclass members are available on the class
|
||||
static_assert(has_member(Dynamic, "__name__"))
|
||||
static_assert(has_member(Dynamic, "__bases__"))
|
||||
static_assert(has_member(Dynamic, "__mro__"))
|
||||
static_assert(has_member(Dynamic, "__subclasses__"))
|
||||
```
|
||||
|
||||
Attributes from the namespace dict (third argument) are not tracked:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
DynamicWithDict = type("DynamicWithDict", (), {"custom_attr": 42})
|
||||
|
||||
# TODO: these should pass -- namespace dict attributes are not yet available for autocomplete
|
||||
static_assert(has_member(DynamicWithDict, "custom_attr")) # error: [static-assert-error]
|
||||
static_assert(has_member(DynamicWithDict(), "custom_attr")) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
Dynamic classes inheriting from classes with custom metaclasses get metaclass members:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class MyMeta(type):
|
||||
meta_attr: str = "meta"
|
||||
|
||||
class Base(metaclass=MyMeta):
|
||||
base_attr: int = 1
|
||||
|
||||
Dynamic = type("Dynamic", (Base,), {})
|
||||
|
||||
# Metaclass attributes are available on the class
|
||||
static_assert(has_member(Dynamic, "meta_attr"))
|
||||
static_assert(has_member(Dynamic, "base_attr"))
|
||||
```
|
||||
|
||||
However, instances of dynamic classes currently do not expose members for autocomplete:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
DynamicSingle = type("DynamicSingle", (Base,), {})
|
||||
instance = DynamicSingle()
|
||||
|
||||
# TODO: these should pass; instance members should be available
|
||||
static_assert(has_member(instance, "base_attr")) # error: [static-assert-error]
|
||||
static_assert(has_member(instance, "__repr__")) # error: [static-assert-error]
|
||||
static_assert(has_member(instance, "__hash__")) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
### Attributes not available at runtime
|
||||
|
||||
Typeshed includes some attributes in `object` that are not available for some (builtin) types. For
|
||||
|
||||
@@ -165,13 +165,14 @@ def _(x: A | B):
|
||||
|
||||
## No narrowing for multiple arguments
|
||||
|
||||
No narrowing should occur if `type` is used to dynamically create a class:
|
||||
Narrowing does not occur in the same way if `type` is used to dynamically create a class:
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
# The following diagnostic is valid, since the three-argument form of `type`
|
||||
# can only be called with `str` as the first argument.
|
||||
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
|
||||
# Inline type() calls fall back to regular type overload matching.
|
||||
# TODO: Once inline type() calls synthesize class types, this should narrow x to Never.
|
||||
#
|
||||
# error: 13 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
|
||||
if type(x, (), {}) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
else:
|
||||
|
||||
@@ -1592,7 +1592,9 @@ pub(crate) mod implicit_globals {
|
||||
use crate::place::{Definedness, PlaceAndQualifiers};
|
||||
use crate::semantic_index::symbol::Symbol;
|
||||
use crate::semantic_index::{place_table, use_def_map};
|
||||
use crate::types::{KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type};
|
||||
use crate::types::{
|
||||
ClassLiteral, KnownClass, MemberLookupPolicy, Parameter, Parameters, Signature, Type,
|
||||
};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::{DefinedPlace, Place, place_from_declarations};
|
||||
@@ -1611,7 +1613,10 @@ pub(crate) mod implicit_globals {
|
||||
else {
|
||||
return Place::Undefined.into();
|
||||
};
|
||||
let module_type_scope = module_type_class.body_scope(db);
|
||||
let Some(class) = module_type_class.as_static() else {
|
||||
return Place::Undefined.into();
|
||||
};
|
||||
let module_type_scope = class.body_scope(db);
|
||||
let place_table = place_table(db, module_type_scope);
|
||||
let Some(symbol_id) = place_table.symbol_id(name) else {
|
||||
return Place::Undefined.into();
|
||||
@@ -1739,8 +1744,10 @@ pub(crate) mod implicit_globals {
|
||||
return smallvec::SmallVec::default();
|
||||
};
|
||||
|
||||
let module_type_scope = module_type.body_scope(db);
|
||||
let module_type_symbol_table = place_table(db, module_type_scope);
|
||||
let ClassLiteral::Static(module_type) = module_type else {
|
||||
return smallvec::SmallVec::default();
|
||||
};
|
||||
let module_type_symbol_table = place_table(db, module_type.body_scope(db));
|
||||
|
||||
module_type_symbol_table
|
||||
.symbols()
|
||||
|
||||
@@ -24,6 +24,7 @@ use ty_module_resolver::{KnownModule, Module, ModuleName, resolve_module};
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::class::DynamicClassLiteral;
|
||||
pub use self::cyclic::CycleDetector;
|
||||
pub(crate) use self::cyclic::{PairVisitor, TypeTransformer};
|
||||
pub(crate) use self::diagnostic::register_lints;
|
||||
@@ -76,7 +77,7 @@ use crate::types::visitor::any_over_type;
|
||||
use crate::unpack::EvaluationMode;
|
||||
use crate::{Db, FxOrderSet, Program};
|
||||
pub use class::KnownClass;
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, StaticClassLiteral};
|
||||
use instance::Protocol;
|
||||
pub use instance::{NominalInstanceType, ProtocolInstanceType};
|
||||
pub use special_form::SpecialFormType;
|
||||
@@ -782,7 +783,7 @@ pub enum Type<'db> {
|
||||
Callable(CallableType<'db>),
|
||||
/// A specific module object
|
||||
ModuleLiteral(ModuleLiteralType<'db>),
|
||||
/// A specific class object
|
||||
/// A specific class object (either from a `class` statement or `type()` call)
|
||||
ClassLiteral(ClassLiteral<'db>),
|
||||
/// A specialization of a generic class
|
||||
GenericAlias(GenericAlias<'db>),
|
||||
@@ -976,9 +977,9 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
fn is_enum(&self, db: &'db dyn Db) -> bool {
|
||||
self.as_nominal_instance()
|
||||
.and_then(|instance| crate::types::enums::enum_metadata(db, instance.class_literal(db)))
|
||||
.is_some()
|
||||
self.as_nominal_instance().is_some_and(|instance| {
|
||||
crate::types::enums::enum_metadata(db, instance.class_literal(db)).is_some()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_typealias_special_form(&self) -> bool {
|
||||
@@ -1097,25 +1098,21 @@ impl<'db> Type<'db> {
|
||||
pub(crate) fn specialization_of(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: ClassLiteral<'_>,
|
||||
expected_class: StaticClassLiteral<'_>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
self.class_and_specialization_of_optional(db, Some(expected_class))
|
||||
.map(|(_, specialization)| specialization)
|
||||
self.specialization_of_optional(db, Some(expected_class))
|
||||
}
|
||||
|
||||
/// If this type is a class instance, returns its class literal and specialization.
|
||||
pub(crate) fn class_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> Option<(ClassLiteral<'db>, Specialization<'db>)> {
|
||||
self.class_and_specialization_of_optional(db, None)
|
||||
/// If this type is a class instance, returns its specialization.
|
||||
pub(crate) fn class_specialization(self, db: &'db dyn Db) -> Option<Specialization<'db>> {
|
||||
self.specialization_of_optional(db, None)
|
||||
}
|
||||
|
||||
fn class_and_specialization_of_optional(
|
||||
fn specialization_of_optional(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
expected_class: Option<ClassLiteral<'_>>,
|
||||
) -> Option<(ClassLiteral<'db>, Specialization<'db>)> {
|
||||
expected_class: Option<StaticClassLiteral<'_>>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
let class_type = match self {
|
||||
Type::NominalInstance(instance) => instance,
|
||||
Type::ProtocolInstance(instance) => instance.to_nominal_instance()?,
|
||||
@@ -1124,12 +1121,12 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
.class(db);
|
||||
|
||||
let (class_literal, specialization) = class_type.class_literal(db);
|
||||
let (class_literal, specialization) = class_type.static_class_literal(db)?;
|
||||
if expected_class.is_some_and(|expected_class| expected_class != class_literal) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((class_literal, specialization?))
|
||||
specialization
|
||||
}
|
||||
|
||||
/// Returns the top materialization (or upper bound materialization) of this type, which is the
|
||||
@@ -2048,7 +2045,10 @@ impl<'db> Type<'db> {
|
||||
|
||||
return;
|
||||
};
|
||||
let (class_literal, Some(specialization)) = instance.class(db).class_literal(db) else {
|
||||
|
||||
let Some((class_literal, Some(specialization))) =
|
||||
instance.class(db).static_class_literal(db)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let generic_context = specialization.generic_context(db);
|
||||
@@ -3248,11 +3248,14 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(name_str, "value" | "_value_")
|
||||
&& is_single_member_enum(db, instance.class(db).class_literal(db).0) =>
|
||||
&& is_single_member_enum(db, instance.class_literal(db)) =>
|
||||
{
|
||||
enum_metadata(db, instance.class(db).class_literal(db).0)
|
||||
.and_then(|metadata| metadata.members.get_index(0).map(|(_, v)| *v))
|
||||
.map_or(Place::Undefined, Place::bound)
|
||||
enum_metadata(db, instance.class_literal(db))
|
||||
.and_then(|metadata| {
|
||||
let (_, ty) = metadata.members.get_index(0)?;
|
||||
Some(Place::bound(*ty))
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -3298,7 +3301,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.map(|outcome| Place::bound(outcome.return_type(db)))
|
||||
// TODO: Handle call errors here.
|
||||
.unwrap_or(Place::Undefined)
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
};
|
||||
|
||||
@@ -3319,7 +3322,7 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
.map(|outcome| Place::bound(outcome.return_type(db)))
|
||||
// TODO: Handle call errors here.
|
||||
.unwrap_or(Place::Undefined)
|
||||
.unwrap_or_default()
|
||||
.into()
|
||||
};
|
||||
|
||||
@@ -3356,14 +3359,15 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..) => {
|
||||
if let Some(enum_class) = match self {
|
||||
let enum_class = match self {
|
||||
Type::ClassLiteral(literal) => Some(literal),
|
||||
Type::SubclassOf(subclass_of) => subclass_of
|
||||
.subclass_of()
|
||||
.into_class(db)
|
||||
.map(|class| class.class_literal(db).0),
|
||||
.map(|class| class.class_literal(db)),
|
||||
_ => None,
|
||||
} {
|
||||
};
|
||||
if let Some(enum_class) = enum_class {
|
||||
if let Some(metadata) = enum_metadata(db, enum_class) {
|
||||
if let Some(resolved_name) = metadata.resolve_member(&name) {
|
||||
return Place::bound(Type::EnumLiteral(EnumLiteralType::new(
|
||||
@@ -5096,7 +5100,9 @@ impl<'db> Type<'db> {
|
||||
let from_class_base = |base: ClassBase<'db>| {
|
||||
let class = base.into_class()?;
|
||||
if class.is_known(db, KnownClass::Generator) {
|
||||
if let Some(specialization) = class.class_literal_specialized(db, None).1 {
|
||||
if let Some((_, Some(specialization))) =
|
||||
class.static_class_literal_specialized(db, None)
|
||||
{
|
||||
if let [_, _, return_ty] = specialization.types(db) {
|
||||
return Some(*return_ty);
|
||||
}
|
||||
@@ -5623,9 +5629,11 @@ impl<'db> Type<'db> {
|
||||
});
|
||||
};
|
||||
|
||||
Ok(typing_self(db, scope_id, typevar_binding_context, class)
|
||||
.map(Type::TypeVar)
|
||||
.unwrap_or(*self))
|
||||
Ok(
|
||||
typing_self(db, scope_id, typevar_binding_context, class.into())
|
||||
.map(Type::TypeVar)
|
||||
.unwrap_or(*self),
|
||||
)
|
||||
}
|
||||
// We ensure that `typing.TypeAlias` used in the expected position (annotating an
|
||||
// annotated assignment statement) doesn't reach here. Using it in any other type
|
||||
@@ -6521,13 +6529,9 @@ impl<'db> Type<'db> {
|
||||
Some(TypeDefinition::Function(function.definition(db)))
|
||||
}
|
||||
Self::ModuleLiteral(module) => Some(TypeDefinition::Module(module.module(db))),
|
||||
Self::ClassLiteral(class_literal) => {
|
||||
Some(TypeDefinition::Class(class_literal.definition(db)))
|
||||
}
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
|
||||
Self::NominalInstance(instance) => {
|
||||
Some(TypeDefinition::Class(instance.class(db).definition(db)))
|
||||
}
|
||||
Self::ClassLiteral(class_literal) => Some(class_literal.type_definition(db)),
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::StaticClass(alias.definition(db))),
|
||||
Self::NominalInstance(instance) => Some(instance.class(db).type_definition(db)),
|
||||
Self::KnownInstance(instance) => match instance {
|
||||
KnownInstanceType::TypeVar(var) => {
|
||||
Some(TypeDefinition::TypeVar(var.definition(db)?))
|
||||
@@ -6540,9 +6544,11 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
|
||||
Self::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||
SubclassOfInner::Dynamic(_) => None,
|
||||
SubclassOfInner::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
SubclassOfInner::Class(class) => Some(class.type_definition(db)),
|
||||
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||
Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?))
|
||||
}
|
||||
},
|
||||
|
||||
Self::TypeAlias(alias) => alias.value_type(db).definition(db),
|
||||
@@ -6569,13 +6575,11 @@ impl<'db> Type<'db> {
|
||||
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
|
||||
Self::ProtocolInstance(protocol) => match protocol.inner {
|
||||
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||
Protocol::FromClass(class) => Some(class.type_definition(db)),
|
||||
Protocol::Synthesized(_) => None,
|
||||
},
|
||||
|
||||
Self::TypedDict(typed_dict) => {
|
||||
typed_dict.definition(db).map(TypeDefinition::Class)
|
||||
}
|
||||
Self::TypedDict(typed_dict) => typed_dict.type_definition(db),
|
||||
|
||||
Self::Union(_) | Self::Intersection(_) => None,
|
||||
|
||||
@@ -6656,7 +6660,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_origin(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
|
||||
pub(crate) fn generic_origin(self, db: &'db dyn Db) -> Option<StaticClassLiteral<'db>> {
|
||||
match self {
|
||||
Type::GenericAlias(generic) => Some(generic.origin(db)),
|
||||
Type::NominalInstance(instance) => {
|
||||
@@ -8985,7 +8989,12 @@ impl<'db> UnionTypeInstance<'db> {
|
||||
) -> Result<impl Iterator<Item = Type<'db>> + 'db, InvalidTypeExpressionError<'db>> {
|
||||
let to_class_literal = |ty: Type<'db>| {
|
||||
ty.as_nominal_instance()
|
||||
.map(|instance| Type::ClassLiteral(instance.class(db).class_literal(db).0))
|
||||
.and_then(|instance| {
|
||||
instance
|
||||
.class(db)
|
||||
.static_class_literal(db)
|
||||
.map(|(lit, _)| Type::ClassLiteral(lit.into()))
|
||||
})
|
||||
.unwrap_or_else(Type::unknown)
|
||||
};
|
||||
|
||||
@@ -11718,7 +11727,7 @@ impl<'db> TypeAliasType<'db> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(super) struct MetaclassCandidate<'db> {
|
||||
metaclass: ClassType<'db>,
|
||||
explicit_metaclass_of: ClassLiteral<'db>,
|
||||
explicit_metaclass_of: StaticClassLiteral<'db>,
|
||||
}
|
||||
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
@@ -12925,7 +12934,7 @@ impl<'db> TypeGuardLike<'db> for TypeGuardType<'db> {
|
||||
/// being added to the given class.
|
||||
pub(super) fn determine_upper_bound<'db>(
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StaticClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
is_known_base: impl Fn(ClassBase<'db>) -> bool,
|
||||
) -> Type<'db> {
|
||||
|
||||
@@ -392,7 +392,7 @@ impl<'db> BoundSuperType<'db> {
|
||||
typevar: TypeVarInstance<'db>,
|
||||
make_owner: fn(BoundTypeVarInstance<'db>, ClassType<'db>) -> SuperOwnerKind<'db>|
|
||||
-> Result<Type<'db>, BoundSuperError<'db>> {
|
||||
let pivot_class_literal = pivot_class.into_class().map(|c| c.class_literal(db).0);
|
||||
let pivot_class_literal = pivot_class.into_class().map(|c| c.class_literal(db));
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for constraint in constraints.elements(db) {
|
||||
let class = match constraint {
|
||||
@@ -409,7 +409,7 @@ impl<'db> BoundSuperType<'db> {
|
||||
| ClassBase::Protocol
|
||||
| ClassBase::TypedDict => false,
|
||||
ClassBase::Class(superclass) => {
|
||||
superclass.class_literal(db).0 == pivot
|
||||
superclass.class_literal(db) == pivot
|
||||
}
|
||||
}) {
|
||||
return Err(BoundSuperError::FailingConditionCheck {
|
||||
@@ -627,11 +627,11 @@ impl<'db> BoundSuperType<'db> {
|
||||
if let Some(pivot_class) = pivot_class.into_class()
|
||||
&& let Some(owner_class) = owner.into_class(db)
|
||||
{
|
||||
let pivot_class = pivot_class.class_literal(db).0;
|
||||
let pivot_class = pivot_class.class_literal(db);
|
||||
if !owner_class.iter_mro(db).any(|superclass| match superclass {
|
||||
ClassBase::Dynamic(_) => true,
|
||||
ClassBase::Generic | ClassBase::Protocol | ClassBase::TypedDict => false,
|
||||
ClassBase::Class(superclass) => superclass.class_literal(db).0 == pivot_class,
|
||||
ClassBase::Class(superclass) => superclass.class_literal(db) == pivot_class,
|
||||
}) {
|
||||
return Err(BoundSuperError::FailingConditionCheck {
|
||||
pivot_class: pivot_class_type,
|
||||
@@ -748,7 +748,7 @@ impl<'db> BoundSuperType<'db> {
|
||||
}
|
||||
};
|
||||
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
let class_literal = class.class_literal(db);
|
||||
// TODO properly support super() with generic types
|
||||
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
|
||||
// * also requires understanding how we should handle cases like this:
|
||||
|
||||
@@ -352,7 +352,7 @@ pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
.any(|element| is_expandable_type(db, element)),
|
||||
Tuple::Variable(_) => false,
|
||||
})
|
||||
|| enum_metadata(db, class.class_literal(db).0).is_some()
|
||||
|| enum_metadata(db, class.class_literal(db)).is_some()
|
||||
}
|
||||
Type::Union(_) => true,
|
||||
_ => false,
|
||||
@@ -403,7 +403,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(enum_members) = enum_member_literals(db, class.class_literal(db).0, None) {
|
||||
if let Some(enum_members) = enum_member_literals(db, class.class_literal(db), None) {
|
||||
return Some(enum_members.collect());
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
||||
use crate::Db;
|
||||
use crate::types::class::CodeGeneratorKind;
|
||||
use crate::types::generics::{ApplySpecialization, Specialization};
|
||||
use crate::types::mro::MroIterator;
|
||||
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType,
|
||||
MaterializationKind, MroError, MroIterator, NormalizedVisitor, SpecialFormType, Type,
|
||||
TypeContext, TypeMapping, todo_type,
|
||||
MaterializationKind, MroError, NormalizedVisitor, SpecialFormType, Type, TypeContext,
|
||||
TypeMapping, todo_type,
|
||||
};
|
||||
|
||||
/// Enumeration of the possible kinds of types we allow in class bases.
|
||||
@@ -245,7 +247,8 @@ impl<'db> ClassBase<'db> {
|
||||
SpecialFormType::Generic => Some(Self::Generic),
|
||||
|
||||
SpecialFormType::NamedTuple => {
|
||||
let fields = subclass.own_fields(db, None, CodeGeneratorKind::NamedTuple);
|
||||
let class = subclass.as_static()?;
|
||||
let fields = class.own_fields(db, None, CodeGeneratorKind::NamedTuple);
|
||||
Self::try_from_type(
|
||||
db,
|
||||
TupleType::heterogeneous(
|
||||
@@ -309,6 +312,16 @@ impl<'db> ClassBase<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the metaclass of this class base.
|
||||
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Self::Class(class) => class.metaclass(db),
|
||||
Self::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||
// TODO: all `Protocol` classes actually have `_ProtocolMeta` as their metaclass.
|
||||
Self::Protocol | Self::Generic | Self::TypedDict => KnownClass::Type.to_instance(db),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping_impl<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -359,7 +372,13 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn has_cyclic_mro(self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
ClassBase::Class(class) => {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
let Some((class_literal, specialization)) = class.static_class_literal(db) else {
|
||||
// Dynamic classes can't have cyclic MRO since their bases must
|
||||
// already exist at creation time. Unlike statement classes, we do not
|
||||
// permit dynamic classes to have forward references in their
|
||||
// bases list.
|
||||
return false;
|
||||
};
|
||||
class_literal
|
||||
.try_mro(db, specialization)
|
||||
.is_err_and(MroError::is_cycle)
|
||||
|
||||
@@ -9,7 +9,10 @@ use ty_module_resolver::Module;
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TypeDefinition<'db> {
|
||||
Module(Module<'db>),
|
||||
Class(Definition<'db>),
|
||||
/// A class created via a `class` statement.
|
||||
StaticClass(Definition<'db>),
|
||||
/// A class created dynamically via `type(name, bases, dict)`.
|
||||
DynamicClass(Definition<'db>),
|
||||
Function(Definition<'db>),
|
||||
TypeVar(Definition<'db>),
|
||||
TypeAlias(Definition<'db>),
|
||||
@@ -21,7 +24,8 @@ impl TypeDefinition<'_> {
|
||||
pub fn focus_range(&self, db: &dyn Db) -> Option<FileRange> {
|
||||
match self {
|
||||
Self::Module(_) => None,
|
||||
Self::Class(definition)
|
||||
Self::StaticClass(definition)
|
||||
| Self::DynamicClass(definition)
|
||||
| Self::Function(definition)
|
||||
| Self::TypeVar(definition)
|
||||
| Self::TypeAlias(definition)
|
||||
@@ -40,7 +44,8 @@ impl TypeDefinition<'_> {
|
||||
let source = source_text(db, file);
|
||||
Some(FileRange::new(file, TextRange::up_to(source.text_len())))
|
||||
}
|
||||
Self::Class(definition)
|
||||
Self::StaticClass(definition)
|
||||
| Self::DynamicClass(definition)
|
||||
| Self::Function(definition)
|
||||
| Self::TypeVar(definition)
|
||||
| Self::TypeAlias(definition)
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::call::CallErrorKind;
|
||||
use super::context::InferContext;
|
||||
use super::mro::DuplicateBaseError;
|
||||
use super::{
|
||||
CallArguments, CallDunderError, ClassBase, ClassLiteral, KnownClass,
|
||||
CallArguments, CallDunderError, ClassBase, ClassLiteral, KnownClass, StaticClassLiteral,
|
||||
add_inferred_python_version_hint_to_diagnostic,
|
||||
};
|
||||
use crate::diagnostic::did_you_mean;
|
||||
@@ -113,6 +113,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&UNRESOLVED_IMPORT);
|
||||
registry.register_lint(&UNRESOLVED_REFERENCE);
|
||||
registry.register_lint(&UNSUPPORTED_BASE);
|
||||
registry.register_lint(&UNSUPPORTED_DYNAMIC_BASE);
|
||||
registry.register_lint(&UNSUPPORTED_OPERATOR);
|
||||
registry.register_lint(&ZERO_STEPSIZE_IN_SLICE);
|
||||
registry.register_lint(&STATIC_ASSERT_ERROR);
|
||||
@@ -841,6 +842,40 @@ declare_lint! {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for dynamic class definitions (using `type()`) that have bases
|
||||
/// which are unsupported by ty.
|
||||
///
|
||||
/// This is equivalent to [`unsupported-base`] but applies to classes created
|
||||
/// via `type()` rather than `class` statements.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a dynamically created class has a base that is an unsupported type
|
||||
/// such as `type[T]`, ty will not be able to resolve the
|
||||
/// [method resolution order] (MRO) for the class. This may lead to an inferior
|
||||
/// understanding of your codebase and unpredictable type-checking behavior.
|
||||
///
|
||||
/// ## Default level
|
||||
/// This rule is disabled by default because it will not cause a runtime error,
|
||||
/// and may be noisy on codebases that use `type()` in highly dynamic ways.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// def factory(base: type[Base]) -> type:
|
||||
/// # `base` has type `type[Base]`, not `type[Base]` itself
|
||||
/// return type("Dynamic", (base,), {}) # error: [unsupported-dynamic-base]
|
||||
/// ```
|
||||
///
|
||||
/// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
/// [`unsupported-base`]: https://docs.astral.sh/ty/rules/unsupported-base
|
||||
pub(crate) static UNSUPPORTED_DYNAMIC_BASE = {
|
||||
summary: "detects dynamic class bases that are unsupported as ty could not feasibly calculate the class's MRO",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Ignore,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for expressions used in `with` statements
|
||||
@@ -2800,12 +2835,12 @@ pub(super) fn report_implicit_return_type(
|
||||
"Only classes that directly inherit from `typing.Protocol` \
|
||||
or `typing_extensions.Protocol` are considered protocol classes",
|
||||
);
|
||||
sub_diagnostic.annotate(
|
||||
Annotation::primary(class.header_span(db)).message(format_args!(
|
||||
sub_diagnostic.annotate(Annotation::primary(class.definition_span(db)).message(
|
||||
format_args!(
|
||||
"`Protocol` not present in `{class}`'s immediate bases",
|
||||
class = class.name(db)
|
||||
)),
|
||||
);
|
||||
),
|
||||
));
|
||||
diagnostic.sub(sub_diagnostic);
|
||||
|
||||
diagnostic.info("See https://typing.python.org/en/latest/spec/protocol.html#");
|
||||
@@ -2974,7 +3009,7 @@ pub(crate) fn report_invalid_exception_cause(context: &InferContext, node: &ast:
|
||||
|
||||
pub(crate) fn report_instance_layout_conflict(
|
||||
context: &InferContext,
|
||||
class: ClassLiteral,
|
||||
class: StaticClassLiteral,
|
||||
node: &ast::StmtClassDef,
|
||||
disjoint_bases: &IncompatibleBases,
|
||||
) {
|
||||
@@ -3009,7 +3044,7 @@ pub(crate) fn report_instance_layout_conflict(
|
||||
|
||||
let span = context.span(&node.bases()[*node_index]);
|
||||
let mut annotation = Annotation::secondary(span.clone());
|
||||
if disjoint_base.class == *originating_base {
|
||||
if originating_base.as_static() == Some(disjoint_base.class) {
|
||||
match disjoint_base.kind {
|
||||
DisjointBaseKind::DefinesSlots => {
|
||||
annotation = annotation.message(format_args!(
|
||||
@@ -3060,6 +3095,32 @@ pub(crate) fn report_instance_layout_conflict(
|
||||
diagnostic.sub(subdiagnostic);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic for a metaclass conflict where both conflicting metaclasses
|
||||
/// are inherited from base classes.
|
||||
pub(super) fn report_conflicting_metaclass_from_bases(
|
||||
context: &InferContext,
|
||||
node: AnyNodeRef,
|
||||
class_name: &str,
|
||||
metaclass1: ClassType,
|
||||
base1: impl std::fmt::Display,
|
||||
metaclass2: ClassType,
|
||||
base2: impl std::fmt::Display,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&CONFLICTING_METACLASS, node) else {
|
||||
return;
|
||||
};
|
||||
let db = context.db();
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The metaclass of a derived class (`{class_name}`) \
|
||||
must be a subclass of the metaclasses of all its bases, \
|
||||
but `{metaclass1}` (metaclass of base class `{base1}`) \
|
||||
and `{metaclass2}` (metaclass of base class `{base2}`) \
|
||||
have no subclass relationship",
|
||||
metaclass1 = metaclass1.name(db),
|
||||
metaclass2 = metaclass2.name(db),
|
||||
));
|
||||
}
|
||||
|
||||
/// Information regarding the conflicting disjoint bases a class is inferred to have in its MRO.
|
||||
///
|
||||
/// For each disjoint base, we record information about which element in the class's bases list
|
||||
@@ -3232,9 +3293,9 @@ pub(crate) fn report_bad_argument_to_protocol_interface(
|
||||
class.name(db)
|
||||
),
|
||||
);
|
||||
class_def_diagnostic.annotate(Annotation::primary(
|
||||
class.class_literal(db).0.header_span(db),
|
||||
));
|
||||
if let Some((class_literal, _)) = class.static_class_literal(db) {
|
||||
class_def_diagnostic.annotate(Annotation::primary(class_literal.header_span(db)));
|
||||
}
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
}
|
||||
|
||||
@@ -3293,7 +3354,7 @@ pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol(
|
||||
),
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol.header_span(db))
|
||||
Annotation::primary(protocol.definition_span(db))
|
||||
.message(format_args!("`{class_name}` declared here")),
|
||||
);
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
@@ -3324,7 +3385,7 @@ pub(crate) fn report_attempted_protocol_instantiation(
|
||||
format_args!("Protocol classes cannot be instantiated"),
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol.header_span(db))
|
||||
Annotation::primary(protocol.definition_span(db))
|
||||
.message(format_args!("`{class_name}` declared as a protocol here")),
|
||||
);
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
@@ -3412,7 +3473,7 @@ pub(crate) fn report_undeclared_protocol_member(
|
||||
leads to an ambiguous interface",
|
||||
);
|
||||
class_def_diagnostic.annotate(
|
||||
Annotation::primary(protocol_class.header_span(db))
|
||||
Annotation::primary(protocol_class.definition_span(db))
|
||||
.message(format_args!("`{class_name}` declared as a protocol here",)),
|
||||
);
|
||||
diagnostic.sub(class_def_diagnostic);
|
||||
@@ -3425,7 +3486,7 @@ pub(crate) fn report_undeclared_protocol_member(
|
||||
|
||||
pub(crate) fn report_duplicate_bases(
|
||||
context: &InferContext,
|
||||
class: ClassLiteral,
|
||||
class: StaticClassLiteral,
|
||||
duplicate_base_error: &DuplicateBaseError,
|
||||
bases_list: &[ast::Expr],
|
||||
) {
|
||||
@@ -3472,7 +3533,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
|
||||
context: &InferContext,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type,
|
||||
class: ClassLiteral,
|
||||
class: StaticClassLiteral,
|
||||
) {
|
||||
let db = context.db();
|
||||
let instance_of_type = KnownClass::Type.to_instance(db);
|
||||
@@ -3582,7 +3643,7 @@ fn report_unsupported_base(
|
||||
context: &InferContext,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type,
|
||||
class: ClassLiteral,
|
||||
class: StaticClassLiteral,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&UNSUPPORTED_BASE, base_node) else {
|
||||
return;
|
||||
@@ -3605,7 +3666,7 @@ fn report_invalid_base<'ctx, 'db>(
|
||||
context: &'ctx InferContext<'db, '_>,
|
||||
base_node: &ast::Expr,
|
||||
base_type: Type<'db>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
) -> Option<LintDiagnosticGuard<'ctx, 'db>> {
|
||||
let builder = context.report_lint(&INVALID_BASE, base_node)?;
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
@@ -3701,7 +3762,7 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
||||
|
||||
pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
(field, field_def): (&str, Option<Definition<'db>>),
|
||||
(field_with_default, field_with_default_def): &(Name, Option<Definition<'db>>),
|
||||
) {
|
||||
@@ -3750,7 +3811,7 @@ pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'
|
||||
|
||||
pub(super) fn report_named_tuple_field_with_leading_underscore<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
field_name: &str,
|
||||
field_definition: Option<Definition<'db>>,
|
||||
) {
|
||||
@@ -3874,7 +3935,7 @@ pub(crate) fn report_cannot_delete_typed_dict_key<'db>(
|
||||
|
||||
pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
node: &ast::StmtClassDef,
|
||||
typevar_with_default: TypeVarInstance<'db>,
|
||||
invalid_later_typevars: &[TypeVarInstance<'db>],
|
||||
@@ -3959,7 +4020,7 @@ pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
pub(crate) fn report_rebound_typevar<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
typevar_name: &ast::name::Name,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
class_node: &ast::StmtClassDef,
|
||||
other_typevar: BoundTypeVarInstance<'db>,
|
||||
) {
|
||||
@@ -4034,10 +4095,8 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
let superclass_name = superclass.name(db);
|
||||
|
||||
let overridden_method = if class_name == superclass_name {
|
||||
format!(
|
||||
"{superclass}.{member}",
|
||||
superclass = superclass.qualified_name(db),
|
||||
)
|
||||
let qualified_name = superclass.qualified_name(db);
|
||||
format!("{qualified_name}.{member}")
|
||||
} else {
|
||||
format!("{superclass_name}.{member}")
|
||||
};
|
||||
@@ -4090,7 +4149,10 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
);
|
||||
}
|
||||
|
||||
let superclass_scope = superclass.class_literal(db).0.body_scope(db);
|
||||
let Some((superclass_literal, _)) = superclass.static_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
|
||||
match superclass_method_kind {
|
||||
MethodKind::NotSynthesized => {
|
||||
@@ -4157,7 +4219,7 @@ pub(super) fn report_invalid_method_override<'db>(
|
||||
};
|
||||
|
||||
sub.annotate(
|
||||
Annotation::primary(superclass.header_span(db))
|
||||
Annotation::primary(superclass.definition_span(db))
|
||||
.message(format_args!("Definition of `{superclass_name}`")),
|
||||
);
|
||||
diagnostic.sub(sub);
|
||||
@@ -4277,9 +4339,10 @@ pub(super) fn report_overridden_final_method<'db>(
|
||||
// but you'd want to delete the `@my_property.deleter` as well as the getter and the deleter,
|
||||
// and we don't model property deleters at all right now.
|
||||
if let Type::FunctionLiteral(function) = subclass_type {
|
||||
let class_node = subclass
|
||||
.class_literal(db)
|
||||
.0
|
||||
let Some((subclass_literal, _)) = subclass.static_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let class_node = subclass_literal
|
||||
.body_scope(db)
|
||||
.node(db)
|
||||
.expect_class()
|
||||
@@ -4577,9 +4640,9 @@ fn report_unsupported_binary_operation_impl<'a>(
|
||||
|
||||
pub(super) fn report_bad_frozen_dataclass_inheritance<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
class: StaticClassLiteral<'db>,
|
||||
class_node: &ast::StmtClassDef,
|
||||
base_class: ClassLiteral<'db>,
|
||||
base_class: StaticClassLiteral<'db>,
|
||||
base_class_node: &ast::Expr,
|
||||
base_class_params: DataclassFlags,
|
||||
) {
|
||||
|
||||
@@ -432,8 +432,12 @@ impl<'db> TypeVisitor<'db> for AmbiguousClassCollector<'db> {
|
||||
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
match ty {
|
||||
Type::ClassLiteral(class) => self.record_class(db, class),
|
||||
Type::EnumLiteral(literal) => self.record_class(db, literal.enum_class(db)),
|
||||
Type::GenericAlias(alias) => self.record_class(db, alias.origin(db)),
|
||||
Type::EnumLiteral(literal) => {
|
||||
self.record_class(db, literal.enum_class(db));
|
||||
}
|
||||
Type::GenericAlias(alias) => {
|
||||
self.record_class(db, ClassLiteral::Static(alias.origin(db)));
|
||||
}
|
||||
// Visit the class (as if it were a nominal-instance type)
|
||||
// rather than the protocol members, if it is a class-based protocol.
|
||||
// (For the purposes of displaying the type, we'll use the class name.)
|
||||
@@ -558,13 +562,15 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||
|
||||
let ty = Type::ClassLiteral(self.class);
|
||||
if qualification_level.is_some() {
|
||||
write!(f.with_type(ty), "{}", self.class.qualified_name(self.db))?;
|
||||
let qualified_name = self.class.qualified_name(self.db);
|
||||
write!(f.with_type(ty), "{qualified_name}")?;
|
||||
} else {
|
||||
write!(f.with_type(ty), "{}", self.class.name(self.db))?;
|
||||
}
|
||||
|
||||
if qualification_level == Some(&QualificationLevel::FileAndLineNumber) {
|
||||
let file = self.class.file(self.db);
|
||||
let class_offset = self.class.header_range(self.db).start();
|
||||
let path = file.path(self.db);
|
||||
let path = match path {
|
||||
FilePath::System(path) => Cow::Owned(FilePath::System(
|
||||
@@ -575,7 +581,6 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||
FilePath::Vendored(_) | FilePath::SystemVirtual(_) => Cow::Borrowed(path),
|
||||
};
|
||||
let line_index = line_index(self.db, file);
|
||||
let class_offset = self.class.header_range(self.db).start();
|
||||
let line_number = line_index.line_index(class_offset);
|
||||
f.set_invalid_type_annotation();
|
||||
write!(f, " @ {path}:{line_number}")?;
|
||||
@@ -1287,7 +1292,7 @@ impl<'db> GenericAlias<'db> {
|
||||
settings: DisplaySettings<'db>,
|
||||
) -> DisplayGenericAlias<'db> {
|
||||
DisplayGenericAlias {
|
||||
origin: self.origin(db),
|
||||
origin: ClassLiteral::Static(self.origin(db)),
|
||||
specialization: self.specialization(db),
|
||||
db,
|
||||
settings,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
Db, FxIndexMap,
|
||||
@@ -9,7 +10,7 @@ use crate::{
|
||||
semantic_index::{place_table, use_def_map},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, DynamicType, EnumLiteralType, KnownClass, MemberLookupPolicy,
|
||||
Type, TypeQualifiers,
|
||||
StaticClassLiteral, Type, TypeQualifiers,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,6 +55,23 @@ pub(crate) fn enum_metadata<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
) -> Option<EnumMetadata<'db>> {
|
||||
let class = match class {
|
||||
ClassLiteral::Static(class) => class,
|
||||
ClassLiteral::Dynamic(..) => {
|
||||
// Classes created via `type` cannot be enums; the following fails at runtime:
|
||||
// ```python
|
||||
// import enum
|
||||
//
|
||||
// class BaseEnum(enum.Enum):
|
||||
// pass
|
||||
//
|
||||
// MyEnum = type("MyEnum", (BaseEnum,), {"A": 1, "B": 2})
|
||||
// ```
|
||||
// TODO: Add a diagnostic for including an enum in a `type(...)` call.
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// This is a fast path to avoid traversing the MRO of known classes
|
||||
if class
|
||||
.known(db)
|
||||
@@ -139,42 +157,43 @@ pub(crate) fn enum_metadata<'db>(
|
||||
auto_counter += 1;
|
||||
|
||||
// `StrEnum`s have different `auto()` behaviour to enums inheriting from `(str, Enum)`
|
||||
let auto_value_ty = if Type::ClassLiteral(class)
|
||||
.is_subtype_of(db, KnownClass::StrEnum.to_subclass_of(db))
|
||||
{
|
||||
Type::string_literal(db, &name.to_lowercase())
|
||||
} else {
|
||||
let custom_mixins: smallvec::SmallVec<[Option<KnownClass>; 1]> =
|
||||
class
|
||||
.iter_mro(db, None)
|
||||
.skip(1)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter(|class| {
|
||||
!Type::from(*class).is_subtype_of(
|
||||
db,
|
||||
KnownClass::Enum.to_subclass_of(db),
|
||||
)
|
||||
})
|
||||
.map(|class| class.known(db))
|
||||
.filter(|class| {
|
||||
!matches!(class, Some(KnownClass::Object))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// `IntEnum`s have the same `auto()` behaviour to enums inheriting from `(int, Enum)`,
|
||||
// and `IntEnum`s also have `int` in their MROs, so both cases are handled here.
|
||||
//
|
||||
// In general, the `auto()` behaviour for enums with non-`int` mixins is hard to predict,
|
||||
// so we fall back to `Any` in those cases.
|
||||
if matches!(
|
||||
custom_mixins.as_slice(),
|
||||
[] | [Some(KnownClass::Int)]
|
||||
) {
|
||||
Type::IntLiteral(auto_counter)
|
||||
let auto_value_ty =
|
||||
if Type::ClassLiteral(ClassLiteral::Static(class))
|
||||
.is_subtype_of(db, KnownClass::StrEnum.to_subclass_of(db))
|
||||
{
|
||||
Type::string_literal(db, &name.to_lowercase())
|
||||
} else {
|
||||
Type::any()
|
||||
}
|
||||
};
|
||||
let custom_mixins: SmallVec<[Option<KnownClass>; 1]> =
|
||||
class
|
||||
.iter_mro(db, None)
|
||||
.skip(1)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter(|class| {
|
||||
!Type::from(*class).is_subtype_of(
|
||||
db,
|
||||
KnownClass::Enum.to_subclass_of(db),
|
||||
)
|
||||
})
|
||||
.map(|class| class.known(db))
|
||||
.filter(|class| {
|
||||
!matches!(class, Some(KnownClass::Object))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// `IntEnum`s have the same `auto()` behaviour to enums inheriting from `(int, Enum)`,
|
||||
// and `IntEnum`s also have `int` in their MROs, so both cases are handled here.
|
||||
//
|
||||
// In general, the `auto()` behaviour for enums with non-`int` mixins is hard to predict,
|
||||
// so we fall back to `Any` in those cases.
|
||||
if matches!(
|
||||
custom_mixins.as_slice(),
|
||||
[] | [Some(KnownClass::Int)]
|
||||
) {
|
||||
Type::IntLiteral(auto_counter)
|
||||
} else {
|
||||
Type::any()
|
||||
}
|
||||
};
|
||||
Some(auto_value_ty)
|
||||
}
|
||||
|
||||
@@ -308,8 +327,12 @@ pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
///
|
||||
/// This is a lighter-weight check than `enum_metadata`, which additionally
|
||||
/// verifies that the class has members.
|
||||
pub(crate) fn is_enum_class_by_inheritance<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
|
||||
Type::ClassLiteral(class).is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
pub(crate) fn is_enum_class_by_inheritance<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: StaticClassLiteral<'db>,
|
||||
) -> bool {
|
||||
Type::ClassLiteral(ClassLiteral::Static(class))
|
||||
.is_subtype_of(db, KnownClass::Enum.to_subclass_of(db))
|
||||
|| class
|
||||
.metaclass(db)
|
||||
.is_subtype_of(db, KnownClass::EnumType.to_subclass_of(db))
|
||||
|
||||
@@ -564,10 +564,11 @@ impl<'db> OverloadLiteral<'db> {
|
||||
let index = semantic_index(db, scope_id.file(db));
|
||||
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
||||
|
||||
let typing_self = typing_self(db, scope_id, typevar_binding_context, class).expect(
|
||||
"We should always find the surrounding class \
|
||||
let typing_self = typing_self(db, scope_id, typevar_binding_context, class.into())
|
||||
.expect(
|
||||
"We should always find the surrounding class \
|
||||
for an implicit self: Self annotation",
|
||||
);
|
||||
);
|
||||
|
||||
if self.is_classmethod(db) {
|
||||
Some(SubclassOfType::from(
|
||||
@@ -1227,10 +1228,7 @@ fn is_instance_truthiness<'db>(
|
||||
.class(db)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|c| match c {
|
||||
ClassType::Generic(c) => c.origin(db) == class,
|
||||
ClassType::NonGeneric(c) => c == class,
|
||||
})
|
||||
.any(|c| c.class_literal(db) == class)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2023,7 +2021,7 @@ impl KnownFunction {
|
||||
if !class.has_ordering_method_in_mro(db) {
|
||||
report_invalid_total_ordering_call(
|
||||
context,
|
||||
class.class_literal(db).0,
|
||||
class.class_literal(db),
|
||||
call_expression,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::semantic_index::{attribute_scopes, global_scope, semantic_index, use_
|
||||
use crate::types::call::{CallArguments, MatchedArgument};
|
||||
use crate::types::signatures::{ParameterKind, Signature};
|
||||
use crate::types::{
|
||||
CallDunderError, CallableTypes, ClassBase, KnownUnion, Type, TypeContext, UnionType,
|
||||
CallDunderError, CallableTypes, ClassBase, ClassLiteral, ClassType, KnownUnion, Type,
|
||||
TypeContext, UnionType,
|
||||
};
|
||||
use crate::{Db, DisplaySettings, HasType, SemanticModel};
|
||||
use ruff_db::files::FileRange;
|
||||
@@ -266,7 +267,10 @@ pub fn definitions_for_attribute<'db>(
|
||||
let class_literal = match meta_type {
|
||||
Type::ClassLiteral(class_literal) => class_literal,
|
||||
Type::SubclassOf(subclass) => match subclass.subclass_of().into_class(db) {
|
||||
Some(cls) => cls.class_literal(db).0,
|
||||
Some(cls) => match cls.static_class_literal(db) {
|
||||
Some((lit, _)) => ClassLiteral::Static(lit),
|
||||
None => continue,
|
||||
},
|
||||
None => continue,
|
||||
},
|
||||
_ => continue,
|
||||
@@ -274,9 +278,9 @@ pub fn definitions_for_attribute<'db>(
|
||||
|
||||
// Walk the MRO: include class and its ancestors, but stop when we find a match
|
||||
'scopes: for ancestor in class_literal
|
||||
.iter_mro(db, None)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|cls| cls.class_literal(db).0)
|
||||
.filter_map(|cls: ClassType<'db>| cls.static_class_literal(db).map(|(lit, _)| lit))
|
||||
{
|
||||
let class_scope = ancestor.body_scope(db);
|
||||
let class_place_table = crate::semantic_index::place_table(db, class_scope);
|
||||
|
||||
@@ -53,7 +53,8 @@ use crate::types::function::FunctionType;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
ClassLiteral, KnownClass, Truthiness, Type, TypeAndQualifiers, declaration_type,
|
||||
ClassLiteral, KnownClass, StaticClassLiteral, Truthiness, Type, TypeAndQualifiers,
|
||||
declaration_type,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use builder::TypeInferenceBuilder;
|
||||
@@ -465,7 +466,7 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||
db: &'db dyn Db,
|
||||
semantic: &SemanticIndex<'db>,
|
||||
scope: ScopeId,
|
||||
) -> Option<ClassLiteral<'db>> {
|
||||
) -> Option<StaticClassLiteral<'db>> {
|
||||
semantic
|
||||
.ancestor_scopes(scope.file_scope_id(db))
|
||||
.find_map(|(_, ancestor_scope)| {
|
||||
@@ -474,6 +475,7 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||
declaration_type(db, definition)
|
||||
.inner_type()
|
||||
.as_class_literal()
|
||||
.and_then(ClassLiteral::as_static)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -53,32 +53,37 @@ use crate::semantic_index::{
|
||||
use crate::subscript::{PyIndex, PySlice};
|
||||
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
|
||||
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
||||
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
|
||||
use crate::types::class::{
|
||||
ClassLiteral, CodeGeneratorKind, DynamicClassLiteral, DynamicMetaclassConflict, FieldKind,
|
||||
MetaclassErrorKind, MethodDecorator,
|
||||
};
|
||||
use crate::types::context::{InNoTypeCheck, InferContext};
|
||||
use crate::types::cyclic::CycleDetector;
|
||||
use crate::types::diagnostic::{
|
||||
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||
CYCLIC_CLASS_DEFINITION, CYCLIC_TYPE_ALIAS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_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,
|
||||
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_OPERATOR, USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions,
|
||||
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,
|
||||
report_duplicate_bases, report_implicit_return_type, report_index_out_of_bounds,
|
||||
report_instance_layout_conflict, report_invalid_arguments_to_annotated,
|
||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||
report_invalid_exception_caught, report_invalid_exception_cause,
|
||||
report_invalid_exception_raised, report_invalid_exception_tuple_caught,
|
||||
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
|
||||
report_invalid_or_unsupported_base, report_invalid_return_type, report_invalid_total_ordering,
|
||||
report_conflicting_metaclass_from_bases, report_duplicate_bases, report_implicit_return_type,
|
||||
report_index_out_of_bounds, report_instance_layout_conflict,
|
||||
report_invalid_arguments_to_annotated, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_exception_caught,
|
||||
report_invalid_exception_cause, report_invalid_exception_raised,
|
||||
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
|
||||
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||
report_invalid_return_type, report_invalid_total_ordering,
|
||||
report_invalid_type_checking_constant, report_invalid_type_param_order,
|
||||
report_named_tuple_field_with_leading_underscore,
|
||||
report_namedtuple_field_without_default_after_field_with_default, report_not_subscriptable,
|
||||
@@ -96,7 +101,7 @@ use crate::types::generics::{
|
||||
};
|
||||
use crate::types::infer::nearest_enclosing_function;
|
||||
use crate::types::instance::SliceLiteral;
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::mro::{DynamicMroErrorKind, MroErrorKind};
|
||||
use crate::types::newtype::NewType;
|
||||
use crate::types::subclass_of::SubclassOfInner;
|
||||
use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType};
|
||||
@@ -107,12 +112,12 @@ use crate::types::typed_dict::{
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
BoundTypeVarIdentity, BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType,
|
||||
CallableTypeKind, ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType,
|
||||
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion,
|
||||
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType,
|
||||
ParamSpecAttrKind, Parameter, ParameterForm, Parameters, Signature, SpecialFormType,
|
||||
SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers,
|
||||
TypeContext, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation,
|
||||
CallableTypeKind, ClassType, DataclassParams, DynamicType, InternedType, IntersectionBuilder,
|
||||
IntersectionType, KnownClass, KnownInstanceType, KnownUnion, LintDiagnosticGuard,
|
||||
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, ParamSpecAttrKind, Parameter,
|
||||
ParameterForm, Parameters, Signature, SpecialFormType, StaticClassLiteral, SubclassOfType,
|
||||
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation,
|
||||
TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
|
||||
TypedDictType, UnionBuilder, UnionType, UnionTypeInstance, binding_type, infer_scope_types,
|
||||
todo_type,
|
||||
@@ -578,27 +583,32 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
);
|
||||
|
||||
if self.db().should_check_file(self.file()) {
|
||||
self.check_class_definitions();
|
||||
self.check_static_class_definitions();
|
||||
self.check_overloaded_functions(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all class definitions to check that the definition will not cause an exception
|
||||
/// to be raised at runtime. This needs to be done after most other types in the scope have been
|
||||
/// inferred, due to the fact that base classes can be deferred. If it looks like a class
|
||||
/// definition is invalid in some way, issue a diagnostic.
|
||||
/// Iterate over all static class definitions (created using `class` statements) to check that
|
||||
/// the definition will not cause an exception to be raised at runtime. This needs to be done
|
||||
/// after most other types in the scope have been inferred, due to the fact that base classes
|
||||
/// can be deferred. If it looks like a class definition is invalid in some way, issue a
|
||||
/// diagnostic.
|
||||
///
|
||||
/// Note: Dynamic classes created via `type()` calls are checked separately during type
|
||||
/// inference of the call expression.
|
||||
///
|
||||
/// Among the things we check for in this method are whether Python will be able to determine a
|
||||
/// consistent "[method resolution order]" and [metaclass] for each class.
|
||||
///
|
||||
/// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
/// [metaclass]: https://docs.python.org/3/reference/datamodel.html#metaclasses
|
||||
fn check_class_definitions(&mut self) {
|
||||
fn check_static_class_definitions(&mut self) {
|
||||
let class_definitions = self.declarations.iter().filter_map(|(definition, ty)| {
|
||||
// Filter out class literals that result from imports
|
||||
if let DefinitionKind::Class(class) = definition.kind(self.db()) {
|
||||
ty.inner_type()
|
||||
.as_class_literal()
|
||||
.and_then(ClassLiteral::as_static)
|
||||
.map(|class_literal| (class_literal, class.node(self.module())))
|
||||
} else {
|
||||
None
|
||||
@@ -625,7 +635,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_named_tuple = CodeGeneratorKind::NamedTuple.matches(self.db(), class, None);
|
||||
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
|
||||
// appears after a field with a default value.
|
||||
@@ -729,7 +740,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
};
|
||||
|
||||
if let Some(disjoint_base) = base_class.nearest_disjoint_base(self.db()) {
|
||||
disjoint_bases.insert(disjoint_base, i, base_class.class_literal(self.db()).0);
|
||||
disjoint_bases.insert(disjoint_base, i, base_class.class_literal(self.db()));
|
||||
}
|
||||
|
||||
if is_protocol
|
||||
@@ -760,12 +771,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
let (base_class_literal, _) = base_class.class_literal(self.db());
|
||||
|
||||
if let (Some(base_params), Some(class_params)) = (
|
||||
base_class_literal.dataclass_params(self.db()),
|
||||
class.dataclass_params(self.db()),
|
||||
) {
|
||||
if let Some((base_class_literal, _)) = base_class.static_class_literal(self.db())
|
||||
&& let (Some(base_params), Some(class_params)) = (
|
||||
base_class_literal.dataclass_params(self.db()),
|
||||
class.dataclass_params(self.db()),
|
||||
)
|
||||
{
|
||||
let base_params = base_params.flags(self.db());
|
||||
let class_is_frozen = class_params.flags(self.db()).is_frozen();
|
||||
|
||||
@@ -864,7 +875,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
function.is_known(self.db(), KnownFunction::TotalOrdering)
|
||||
})
|
||||
}) {
|
||||
report_invalid_total_ordering(&self.context, class, decorator);
|
||||
report_invalid_total_ordering(
|
||||
&self.context,
|
||||
ClassLiteral::Static(class),
|
||||
decorator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,35 +930,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
},
|
||||
candidate1_is_base_class,
|
||||
} => {
|
||||
if let Some(builder) =
|
||||
if *candidate1_is_base_class {
|
||||
report_conflicting_metaclass_from_bases(
|
||||
&self.context,
|
||||
class_node.into(),
|
||||
class.name(self.db()),
|
||||
*metaclass1,
|
||||
class1.name(self.db()),
|
||||
*metaclass2,
|
||||
class2.name(self.db()),
|
||||
);
|
||||
} else if let Some(builder) =
|
||||
self.context.report_lint(&CONFLICTING_METACLASS, class_node)
|
||||
{
|
||||
if *candidate1_is_base_class {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The metaclass of a derived class (`{class}`) \
|
||||
must be a subclass of the metaclasses of all its bases, \
|
||||
but `{metaclass1}` (metaclass of base class `{base1}`) \
|
||||
and `{metaclass2}` (metaclass of base class `{base2}`) \
|
||||
have no subclass relationship",
|
||||
class = class.name(self.db()),
|
||||
metaclass1 = metaclass1.name(self.db()),
|
||||
base1 = class1.name(self.db()),
|
||||
metaclass2 = metaclass2.name(self.db()),
|
||||
base2 = class2.name(self.db()),
|
||||
));
|
||||
} else {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The metaclass of a derived class (`{class}`) \
|
||||
builder.into_diagnostic(format_args!(
|
||||
"The metaclass of a derived class (`{class}`) \
|
||||
must be a subclass of the metaclasses of all its bases, \
|
||||
but `{metaclass_of_class}` (metaclass of `{class}`) \
|
||||
and `{metaclass_of_base}` (metaclass of base class `{base}`) \
|
||||
have no subclass relationship",
|
||||
class = class.name(self.db()),
|
||||
metaclass_of_class = metaclass1.name(self.db()),
|
||||
metaclass_of_base = metaclass2.name(self.db()),
|
||||
base = class2.name(self.db()),
|
||||
));
|
||||
}
|
||||
class = class.name(self.db()),
|
||||
metaclass_of_class = metaclass1.name(self.db()),
|
||||
metaclass_of_base = metaclass2.name(self.db()),
|
||||
base = class2.name(self.db()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,7 +1040,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
// (7) 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, None)
|
||||
CodeGeneratorKind::from_class(self.db(), class.into(), None)
|
||||
{
|
||||
let specialization = None;
|
||||
|
||||
@@ -2979,7 +2989,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple)
|
||||
}
|
||||
(None, "Any") if in_typing_module() => Type::SpecialForm(SpecialFormType::Any),
|
||||
_ => Type::from(ClassLiteral::new(
|
||||
_ => Type::from(StaticClassLiteral::new(
|
||||
self.db(),
|
||||
name.id.clone(),
|
||||
body_scope,
|
||||
@@ -4285,10 +4295,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if object_ty
|
||||
.as_nominal_instance()
|
||||
.and_then(|instance| {
|
||||
file_to_module(
|
||||
db,
|
||||
instance.class(db).class_literal(db).0.file(db),
|
||||
)
|
||||
instance.class(db).static_class_literal(db)
|
||||
})
|
||||
.and_then(|(class_literal, _)| {
|
||||
file_to_module(db, class_literal.file(db))
|
||||
})
|
||||
.and_then(|module| module.search_path(db))
|
||||
.is_some_and(ty_module_resolver::SearchPath::is_first_party)
|
||||
@@ -4625,9 +4635,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
// Check if class-level attribute already has a value
|
||||
{
|
||||
let class_definition = class_ty.class_literal(db).0;
|
||||
let class_scope_id = class_definition.body_scope(db).file_scope_id(db);
|
||||
if let Some((class_literal, _)) = class_ty.static_class_literal(db) {
|
||||
let class_scope_id = class_literal.body_scope(db).file_scope_id(db);
|
||||
let place_table = builder.index.place_table(class_scope_id);
|
||||
|
||||
if let Some(symbol) = place_table.symbol_by_name(attribute) {
|
||||
@@ -5399,6 +5408,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Some(KnownClass::NewType) => {
|
||||
self.infer_newtype_expression(target, call_expr, definition)
|
||||
}
|
||||
Some(KnownClass::Type) => {
|
||||
// Try to extract the dynamic class with definition.
|
||||
// This returns `None` if it's not a three-arg call to `type()`,
|
||||
// signalling that we must fall back to normal call inference.
|
||||
self.infer_dynamic_type_expression(call_expr, definition)
|
||||
.unwrap_or_else(|| {
|
||||
self.infer_call_expression_impl(call_expr, callable_type, tcx)
|
||||
})
|
||||
}
|
||||
Some(_) | None => {
|
||||
self.infer_call_expression_impl(call_expr, callable_type, tcx)
|
||||
}
|
||||
@@ -6002,6 +6020,245 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to infer a 3-argument `type(name, bases, dict)` call expression, capturing the definition.
|
||||
///
|
||||
/// This is called when we detect a `type()` call in assignment context and want to
|
||||
/// associate the resulting `DynamicClassLiteral` with its definition for go-to-definition.
|
||||
///
|
||||
/// Returns `None` if any keywords were provided or the number of arguments is not three,
|
||||
/// signalling that no types were stored for any AST sub-expressions and that we should
|
||||
/// therefore fallback to normal call binding for error reporting.
|
||||
fn infer_dynamic_type_expression(
|
||||
&mut self,
|
||||
call_expr: &ast::ExprCall,
|
||||
definition: Definition<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let db = self.db();
|
||||
|
||||
let ast::Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = &call_expr.arguments;
|
||||
|
||||
if !keywords.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let [name_arg, bases_arg, namespace_arg] = &**args else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// If any argument is a starred expression, we can't know how many positional arguments
|
||||
// we're receiving, so fall back to normal call binding.
|
||||
if args.iter().any(ast::Expr::is_starred_expr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Infer the argument types.
|
||||
let name_type = self.infer_expression(name_arg, TypeContext::default());
|
||||
let bases_type = self.infer_expression(bases_arg, TypeContext::default());
|
||||
let namespace_type = self.infer_expression(namespace_arg, TypeContext::default());
|
||||
|
||||
if !namespace_type.is_assignable_to(
|
||||
db,
|
||||
KnownClass::Dict
|
||||
.to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()]),
|
||||
) && let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_ARGUMENT_TYPE, namespace_arg)
|
||||
{
|
||||
let mut diagnostic = builder
|
||||
.into_diagnostic("Invalid argument to parameter 3 (`namespace`) of `type()`");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Expected `dict[str, Any]`, found `{}`",
|
||||
namespace_type.display(db)
|
||||
));
|
||||
}
|
||||
|
||||
// Extract name and base classes.
|
||||
let name = if let Type::StringLiteral(literal) = name_type {
|
||||
ast::name::Name::new(literal.value(db))
|
||||
} else {
|
||||
if !name_type.is_assignable_to(db, KnownClass::Str.to_instance(db))
|
||||
&& let Some(builder) = self.context.report_lint(&INVALID_ARGUMENT_TYPE, name_arg)
|
||||
{
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("Invalid argument to parameter 1 (`name`) of `type()`");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Expected `str`, found `{}`",
|
||||
name_type.display(db)
|
||||
));
|
||||
}
|
||||
ast::name::Name::new_static("<unknown>")
|
||||
};
|
||||
|
||||
let bases = self.extract_dynamic_type_bases(bases_arg, bases_type, &name);
|
||||
|
||||
let dynamic_class = DynamicClassLiteral::new(db, name, bases, definition, None);
|
||||
|
||||
// Check for MRO errors.
|
||||
if let Err(error) = dynamic_class.try_mro(db) {
|
||||
match error.reason() {
|
||||
DynamicMroErrorKind::DuplicateBases(duplicates) => {
|
||||
if let Some(builder) = self.context.report_lint(&DUPLICATE_BASE, call_expr) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Duplicate base class{maybe_s} {dupes} in class `{class}`",
|
||||
maybe_s = if duplicates.len() == 1 { "" } else { "es" },
|
||||
dupes = duplicates
|
||||
.iter()
|
||||
.map(|base: &ClassBase<'_>| base.display(db))
|
||||
.join(", "),
|
||||
class = dynamic_class.name(db),
|
||||
));
|
||||
}
|
||||
}
|
||||
DynamicMroErrorKind::UnresolvableMro => {
|
||||
if let Some(builder) = self.context.report_lint(&INCONSISTENT_MRO, call_expr) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot create a consistent method resolution order (MRO) \
|
||||
for class `{}` with bases `[{}]`",
|
||||
dynamic_class.name(db),
|
||||
dynamic_class
|
||||
.bases(db)
|
||||
.iter()
|
||||
.map(|base| base.display(db))
|
||||
.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for metaclass conflicts.
|
||||
if let Err(DynamicMetaclassConflict {
|
||||
metaclass1,
|
||||
base1,
|
||||
metaclass2,
|
||||
base2,
|
||||
}) = dynamic_class.try_metaclass(db)
|
||||
{
|
||||
report_conflicting_metaclass_from_bases(
|
||||
&self.context,
|
||||
call_expr.into(),
|
||||
dynamic_class.name(db),
|
||||
metaclass1,
|
||||
base1.display(db),
|
||||
metaclass2,
|
||||
base2.display(db),
|
||||
);
|
||||
}
|
||||
|
||||
Some(Type::ClassLiteral(ClassLiteral::Dynamic(dynamic_class)))
|
||||
}
|
||||
|
||||
/// Extract base classes from the second argument of a `type()` call.
|
||||
///
|
||||
/// If any bases were invalid, diagnostics are emitted and the dynamic
|
||||
/// class is inferred as inheriting from `Unknown`.
|
||||
fn extract_dynamic_type_bases(
|
||||
&mut self,
|
||||
bases_node: &ast::Expr,
|
||||
bases_type: Type<'db>,
|
||||
name: &ast::name::Name,
|
||||
) -> Box<[ClassBase<'db>]> {
|
||||
let db = self.db();
|
||||
|
||||
// Get AST nodes for base expressions (for diagnostics).
|
||||
let bases_tuple_elts = bases_node.as_tuple_expr().map(|t| t.elts.as_slice());
|
||||
|
||||
// We use a placeholder class literal for try_from_type (the subclass parameter is only
|
||||
// used for Protocol/TypedDict detection which doesn't apply here).
|
||||
let placeholder_class: ClassLiteral<'db> =
|
||||
KnownClass::Object.try_to_class_literal(db).unwrap().into();
|
||||
|
||||
bases_type
|
||||
.tuple_instance_spec(db)
|
||||
.as_deref()
|
||||
.and_then(|spec| spec.as_fixed_length())
|
||||
.map(|tuple| {
|
||||
// Fixed-length tuple: extract each base class
|
||||
tuple
|
||||
.elements_slice()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, base)| {
|
||||
// First try the standard conversion.
|
||||
if let Some(class_base) =
|
||||
ClassBase::try_from_type(db, *base, placeholder_class)
|
||||
{
|
||||
return class_base;
|
||||
}
|
||||
|
||||
let diagnostic_node = bases_tuple_elts
|
||||
.and_then(|elts| elts.get(idx))
|
||||
.unwrap_or(bases_node);
|
||||
|
||||
// If that fails, check if the type is "type-like" (e.g., `type[Base]`).
|
||||
// For type-like bases we emit `unsupported-dynamic-base` and use
|
||||
// `Unknown` to avoid cascading errors. For non-type-like bases (like
|
||||
// integers), we return `None` to fall through to regular call binding
|
||||
// which will emit `invalid-argument-type`.
|
||||
let instance_of_type = KnownClass::Type.to_instance(db);
|
||||
|
||||
if base.is_assignable_to(db, instance_of_type) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&UNSUPPORTED_DYNAMIC_BASE, diagnostic_node)
|
||||
{
|
||||
let mut diagnostic =
|
||||
builder.into_diagnostic("Unsupported class base");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Has type `{}`",
|
||||
base.display(db)
|
||||
));
|
||||
diagnostic.info(format_args!(
|
||||
"ty cannot determine a MRO for class `{name}` due to this base"
|
||||
));
|
||||
diagnostic.info(
|
||||
"Only class objects or `Any` are supported as class bases",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_BASE, diagnostic_node)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Invalid class base with type `{}`",
|
||||
base.display(db)
|
||||
));
|
||||
if bases_tuple_elts.is_none() {
|
||||
diagnostic.info(format_args!(
|
||||
"Element {} of the tuple is invalid",
|
||||
idx + 1
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassBase::unknown()
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if !bases_type.is_assignable_to(
|
||||
db,
|
||||
Type::homogeneous_tuple(db, KnownClass::Type.to_instance(db)),
|
||||
) && let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ARGUMENT_TYPE, bases_node)
|
||||
{
|
||||
let mut diagnostic = builder
|
||||
.into_diagnostic("Invalid argument to parameter 2 (`bases`) of `type()`");
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Expected `tuple[type, ...]`, found `{}`",
|
||||
bases_type.display(db)
|
||||
));
|
||||
}
|
||||
Box::from([ClassBase::unknown()])
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
|
||||
if assignment.target.is_name_expr() {
|
||||
self.infer_definition(assignment);
|
||||
@@ -6145,14 +6402,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let class_literal = infer_definition_types(db, class_definition)
|
||||
.declaration_type(class_definition)
|
||||
.inner_type()
|
||||
.as_class_literal()?;
|
||||
.as_class_literal()?
|
||||
.as_static()?;
|
||||
|
||||
class_literal
|
||||
.dataclass_params(db)
|
||||
.map(|params| SmallVec::from(params.field_specifiers(db)))
|
||||
.or_else(|| {
|
||||
Some(SmallVec::from(
|
||||
CodeGeneratorKind::from_class(db, class_literal, None)?
|
||||
CodeGeneratorKind::from_class(db, class_literal.into(), None)?
|
||||
.dataclass_transformer_params()?
|
||||
.field_specifiers(db),
|
||||
))
|
||||
@@ -8962,11 +9220,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// are handled by the default constructor-call logic (we synthesize a `__new__` method for them
|
||||
// in `ClassType::own_class_member()`).
|
||||
class.is_known(self.db(), KnownClass::Tuple) && !class.is_generic()
|
||||
) || CodeGeneratorKind::TypedDict.matches(
|
||||
self.db(),
|
||||
class.class_literal(self.db()).0,
|
||||
class.class_literal(self.db()).1,
|
||||
);
|
||||
) || class
|
||||
.static_class_literal(self.db())
|
||||
.is_some_and(|(class_literal, specialization)| {
|
||||
CodeGeneratorKind::TypedDict.matches(
|
||||
self.db(),
|
||||
class_literal.into(),
|
||||
specialization,
|
||||
)
|
||||
});
|
||||
|
||||
// temporary special-casing for all subclasses of `enum.Enum`
|
||||
// until we support the functional syntax for creating enum classes
|
||||
@@ -11936,7 +12198,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(generic_context) = class.generic_context(self.db()) {
|
||||
if let Some(generic_context) = class.generic_context(self.db())
|
||||
&& let Some(class) = class.as_static()
|
||||
{
|
||||
return self.infer_explicit_class_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
@@ -12273,7 +12537,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
value_ty: Type<'db>,
|
||||
generic_class: ClassLiteral<'db>,
|
||||
generic_class: StaticClassLiteral<'db>,
|
||||
generic_context: GenericContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
@@ -12991,7 +13255,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// TODO: properly handle old-style generics; get rid of this temporary hack
|
||||
if !value_ty
|
||||
.as_class_literal()
|
||||
.is_some_and(|class| class.iter_mro(db, None).contains(&ClassBase::Generic))
|
||||
.is_some_and(|class| class.iter_mro(db).contains(&ClassBase::Generic))
|
||||
{
|
||||
report_not_subscriptable(context, subscript, value_ty, "__class_getitem__");
|
||||
}
|
||||
|
||||
@@ -1045,12 +1045,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
value_ty
|
||||
}
|
||||
Type::ClassLiteral(class) => {
|
||||
match class.generic_context(self.db()) {
|
||||
Some(generic_context) => {
|
||||
match (class.generic_context(self.db()), class.as_static()) {
|
||||
(Some(generic_context), Some(static_class)) => {
|
||||
let specialized_class = self.infer_explicit_class_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
class,
|
||||
static_class,
|
||||
generic_context,
|
||||
);
|
||||
|
||||
@@ -1062,7 +1062,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
)
|
||||
.unwrap_or(Type::unknown())
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
// TODO: emit a diagnostic if you try to specialize a non-generic class.
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("specialized non-generic class")
|
||||
|
||||
@@ -35,27 +35,39 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
match class_literal.known(db) {
|
||||
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
|
||||
db,
|
||||
specialization
|
||||
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
|
||||
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
|
||||
.as_ref(),
|
||||
)),
|
||||
Some(KnownClass::Object) => Type::object(),
|
||||
_ => class_literal
|
||||
.is_typed_dict(db)
|
||||
.then(|| Type::typed_dict(class))
|
||||
.or_else(|| {
|
||||
class.into_protocol_class(db).map(|protocol_class| {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(protocol_class))
|
||||
})
|
||||
})
|
||||
.unwrap_or(Type::NominalInstance(NominalInstanceType(
|
||||
NominalInstanceInner::NonTuple(class),
|
||||
))),
|
||||
match class.class_literal(db) {
|
||||
// Dynamic classes created via `type()` don't have special instance types.
|
||||
// TODO: When we add functional TypedDict support, this branch should check
|
||||
// for TypedDict and return `Type::typed_dict(class)` for that case.
|
||||
ClassLiteral::Dynamic(_) => {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
}
|
||||
ClassLiteral::Static(class_literal) => {
|
||||
let specialization = class.into_generic_alias().map(|g| g.specialization(db));
|
||||
match class_literal.known(db) {
|
||||
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
|
||||
db,
|
||||
specialization
|
||||
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
|
||||
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
|
||||
.as_ref(),
|
||||
)),
|
||||
Some(KnownClass::Object) => Type::object(),
|
||||
_ => class_literal
|
||||
.is_typed_dict(db)
|
||||
.then(|| Type::typed_dict(class))
|
||||
.or_else(|| {
|
||||
class.into_protocol_class(db).map(|protocol_class| {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(
|
||||
protocol_class,
|
||||
))
|
||||
})
|
||||
})
|
||||
.unwrap_or(Type::NominalInstance(NominalInstanceType(
|
||||
NominalInstanceInner::NonTuple(class),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,18 +237,9 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the class literal for this instance.
|
||||
pub(super) fn class_literal(&self, db: &'db dyn Db) -> ClassLiteral<'db> {
|
||||
let class = match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
NominalInstanceInner::Object => {
|
||||
return KnownClass::Object
|
||||
.try_to_class_literal(db)
|
||||
.expect("Typeshed should always have a `object` class in `builtins.pyi`");
|
||||
}
|
||||
};
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
class_literal
|
||||
self.class(db).class_literal(db)
|
||||
}
|
||||
|
||||
/// Returns the [`KnownClass`] that this is a nominal instance of, or `None` if it is not an
|
||||
@@ -275,7 +278,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
.find_map(|class| match class.known(db)? {
|
||||
// N.B. this is a pure optimisation: iterating through the MRO would give us
|
||||
// the correct tuple spec for `sys._version_info`, since we special-case the class
|
||||
// in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from
|
||||
// in `StmtClassLiteral::explicit_bases()` so that it is inferred as inheriting from
|
||||
// a tuple type with the correct spec for the user's configured Python version and platform.
|
||||
KnownClass::VersionInfo => {
|
||||
Some(Cow::Owned(TupleSpec::version_info_spec(db)))
|
||||
@@ -337,10 +340,9 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::ExactTuple(_) | NominalInstanceInner::Object => return None,
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
};
|
||||
let (class, Some(specialization)) = class.class_literal(db) else {
|
||||
return None;
|
||||
};
|
||||
if !class.is_known(db, KnownClass::Slice) {
|
||||
let (class_literal, specialization) = class.static_class_literal(db)?;
|
||||
let specialization = specialization?;
|
||||
if !class_literal.is_known(db, KnownClass::Slice) {
|
||||
return None;
|
||||
}
|
||||
let [start, stop, step] = specialization.types(db) else {
|
||||
@@ -480,8 +482,13 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.or(db, || {
|
||||
ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)))
|
||||
ConstraintSet::from(
|
||||
!self
|
||||
.class(db)
|
||||
.could_coexist_in_mro_with(db, other.class(db)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -496,7 +503,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.map(KnownClass::is_singleton)
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +515,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
.known(db)
|
||||
.and_then(KnownClass::is_single_valued)
|
||||
.or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db)))
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +628,7 @@ pub(super) fn walk_protocol_instance_type<'db, V: super::visitor::TypeVisitor<'d
|
||||
} else {
|
||||
match protocol.inner {
|
||||
Protocol::FromClass(class) => {
|
||||
if let Some(specialization) = class.class_literal(db).1 {
|
||||
if let Some((_, Some(specialization))) = class.static_class_literal(db) {
|
||||
walk_specialization(db, specialization, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ use crate::{
|
||||
semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, SubclassOfInner, Type,
|
||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind, generics::Specialization,
|
||||
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, StaticClassLiteral,
|
||||
SubclassOfInner, Type, TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
||||
generics::Specialization,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -201,9 +202,20 @@ impl<'db> AllMembers<'db> {
|
||||
),
|
||||
|
||||
Type::NominalInstance(instance) => {
|
||||
let (class_literal, specialization) = instance.class(db).class_literal(db);
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||
let class = instance.class(db);
|
||||
if let Some((class_literal, specialization)) = class.static_class_literal(db) {
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Static(class_literal),
|
||||
specialization,
|
||||
);
|
||||
} else {
|
||||
// For dynamic classes, we can't enumerate instance members (requires body scope),
|
||||
// but we can still add synthetic members for dataclass-like classes.
|
||||
self.extend_with_synthetic_members(db, ty, class.class_literal(db), None);
|
||||
}
|
||||
}
|
||||
|
||||
Type::NewTypeInstance(newtype) => {
|
||||
@@ -232,8 +244,13 @@ impl<'db> AllMembers<'db> {
|
||||
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, None);
|
||||
self.extend_with_class_members(db, ty, ClassLiteral::Static(class_literal));
|
||||
self.extend_with_synthetic_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Static(class_literal),
|
||||
None,
|
||||
);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
}
|
||||
@@ -245,11 +262,23 @@ impl<'db> AllMembers<'db> {
|
||||
}
|
||||
_ => {
|
||||
if let Some(class_type) = subclass_of_type.subclass_of().into_class(db) {
|
||||
let (class_literal, specialization) = class_type.class_literal(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
if let Some((class_literal, specialization)) =
|
||||
class_type.static_class_literal(db)
|
||||
{
|
||||
self.extend_with_class_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Static(class_literal),
|
||||
);
|
||||
self.extend_with_synthetic_members(
|
||||
db,
|
||||
ty,
|
||||
ClassLiteral::Static(class_literal),
|
||||
specialization,
|
||||
);
|
||||
if let Type::ClassLiteral(metaclass) = class_literal.metaclass(db) {
|
||||
self.extend_with_class_members(db, ty, metaclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,13 +337,15 @@ impl<'db> AllMembers<'db> {
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
if let Some(class) = subclass_of.subclass_of().into_class(db) {
|
||||
self.extend_with_class_members(db, ty, class.class_literal(db).0);
|
||||
if let Some(class) = subclass_of.subclass_of().into_class(db)
|
||||
&& let Some((class_literal, _)) = class.static_class_literal(db)
|
||||
{
|
||||
self.extend_with_class_members(db, ty, ClassLiteral::Static(class_literal));
|
||||
}
|
||||
}
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
let class_literal = generic_alias.origin(db);
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
self.extend_with_class_members(db, ty, ClassLiteral::Static(class_literal));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -324,7 +355,7 @@ impl<'db> AllMembers<'db> {
|
||||
self.extend_with_class_members(db, ty, class_literal);
|
||||
}
|
||||
|
||||
if let Type::ClassLiteral(class) =
|
||||
if let Type::ClassLiteral(ClassLiteral::Static(class)) =
|
||||
KnownClass::TypedDictFallback.to_class_literal(db)
|
||||
{
|
||||
self.extend_with_instance_members(db, ty, class);
|
||||
@@ -430,9 +461,9 @@ impl<'db> AllMembers<'db> {
|
||||
class_literal: ClassLiteral<'db>,
|
||||
) {
|
||||
for parent in class_literal
|
||||
.iter_mro(db, None)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|class| class.class_literal(db).0)
|
||||
.filter_map(|class| class.static_class_literal(db).map(|(lit, _)| lit))
|
||||
{
|
||||
let parent_scope = parent.body_scope(db);
|
||||
for memberdef in all_end_of_scope_members(db, parent_scope) {
|
||||
@@ -448,52 +479,64 @@ impl<'db> AllMembers<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_instance_members(
|
||||
/// Extend with instance members from a single class (not its MRO).
|
||||
fn extend_with_instance_members_for_class(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StaticClassLiteral<'db>,
|
||||
) {
|
||||
for parent in class_literal
|
||||
.iter_mro(db, None)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.map(|class| class.class_literal(db).0)
|
||||
{
|
||||
let class_body_scope = parent.body_scope(db);
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
for place_expr in index.place_table(function_scope_id).members() {
|
||||
let Some(name) = place_expr.as_instance_attribute() else {
|
||||
continue;
|
||||
};
|
||||
let result = ty.member(db, name);
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: Name::new(name),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is very similar to `extend_with_class_members`,
|
||||
// but uses the type of the class instance to query the
|
||||
// class member. This gets us the right type for each
|
||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||
for memberdef in all_end_of_scope_members(db, class_body_scope) {
|
||||
let result = ty.member(db, memberdef.member.name.as_str());
|
||||
let class_body_scope = class_literal.body_scope(db);
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
for place_expr in index.place_table(function_scope_id).members() {
|
||||
let Some(name) = place_expr.as_instance_attribute() else {
|
||||
continue;
|
||||
};
|
||||
let result = ty.member(db, name);
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: memberdef.member.name,
|
||||
name: Name::new(name),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is very similar to `extend_with_class_members`,
|
||||
// but uses the type of the class instance to query the
|
||||
// class member. This gets us the right type for each
|
||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||
for memberdef in all_end_of_scope_members(db, class_body_scope) {
|
||||
let result = ty.member(db, memberdef.member.name.as_str());
|
||||
let Some(ty) = result.place.ignore_possibly_undefined() else {
|
||||
continue;
|
||||
};
|
||||
self.members.insert(Member {
|
||||
name: memberdef.member.name,
|
||||
ty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend with instance members from a class and all classes in its MRO.
|
||||
fn extend_with_instance_members(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
class_literal: StaticClassLiteral<'db>,
|
||||
) {
|
||||
for class in class_literal
|
||||
.iter_mro(db, None)
|
||||
.filter_map(ClassBase::into_class)
|
||||
{
|
||||
if let Some((class_literal, _)) = class.static_class_literal(db) {
|
||||
self.extend_with_instance_members_for_class(db, ty, class_literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_synthetic_members(
|
||||
|
||||
@@ -2,17 +2,20 @@ use std::collections::VecDeque;
|
||||
use std::ops::Deref;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::{ClassLiteral, ClassType, KnownClass, KnownInstanceType, SpecialFormType, Type};
|
||||
use crate::types::{
|
||||
ClassLiteral, ClassType, DynamicClassLiteral, KnownInstanceType, SpecialFormType,
|
||||
StaticClassLiteral, Type,
|
||||
};
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
///
|
||||
/// An MRO cannot contain non-specialized generic classes. (This is why [`ClassBase`] contains a
|
||||
/// [`ClassType`], not a [`ClassLiteral`].) Any generic classes in a base class list are always
|
||||
/// [`ClassType`], not a [`StaticClassLiteral`].) Any generic classes in a base class list are always
|
||||
/// specialized — either because the class is explicitly specialized if there is a subscript
|
||||
/// expression, or because we create the default specialization if there isn't.
|
||||
///
|
||||
@@ -29,12 +32,12 @@ use crate::types::{ClassLiteral, ClassType, KnownClass, KnownInstanceType, Speci
|
||||
///
|
||||
/// See [`ClassType::iter_mro`] for more details.
|
||||
#[derive(PartialEq, Eq, Clone, Debug, salsa::Update, get_size2::GetSize)]
|
||||
pub(super) struct Mro<'db>(Box<[ClassBase<'db>]>);
|
||||
pub(crate) struct Mro<'db>(Box<[ClassBase<'db>]>);
|
||||
|
||||
impl<'db> Mro<'db> {
|
||||
/// Attempt to resolve the MRO of a given class. Because we derive the MRO from the list of
|
||||
/// base classes in the class definition, this operation is performed on a [class
|
||||
/// literal][ClassLiteral], not a [class type][ClassType]. (You can _also_ get the MRO of a
|
||||
/// literal][StaticClassLiteral], not a [class type][ClassType]. (You can _also_ get the MRO of a
|
||||
/// class type, but this is done by first getting the MRO of the underlying class literal, and
|
||||
/// specializing each base class as needed if the class type is a generic alias.)
|
||||
///
|
||||
@@ -46,35 +49,11 @@ impl<'db> Mro<'db> {
|
||||
///
|
||||
/// (We emit a diagnostic warning about the runtime `TypeError` in
|
||||
/// [`super::infer::infer_scope_types`].)
|
||||
pub(super) fn of_class(
|
||||
pub(super) fn of_static_class(
|
||||
db: &'db dyn Db,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
class_literal: StaticClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Self, MroError<'db>> {
|
||||
let class = class_literal.apply_optional_specialization(db, specialization);
|
||||
// Special-case `NotImplementedType`: typeshed says that it inherits from `Any`,
|
||||
// but this causes more problems than it fixes.
|
||||
if class_literal.is_known(db, KnownClass::NotImplementedType) {
|
||||
return Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)]));
|
||||
}
|
||||
Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization)
|
||||
.map_err(|err| err.into_mro_error(db, class))
|
||||
}
|
||||
|
||||
pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
Self::from([
|
||||
ClassBase::Class(class),
|
||||
ClassBase::unknown(),
|
||||
ClassBase::object(db),
|
||||
])
|
||||
}
|
||||
|
||||
fn of_class_impl(
|
||||
db: &'db dyn Db,
|
||||
class: ClassType<'db>,
|
||||
original_bases: &[Type<'db>],
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Self, MroErrorKind<'db>> {
|
||||
/// Possibly add `Generic` to the resolved bases list.
|
||||
///
|
||||
/// This function is called in two cases:
|
||||
@@ -106,6 +85,10 @@ impl<'db> Mro<'db> {
|
||||
resolved_bases.push(ClassBase::Generic);
|
||||
}
|
||||
|
||||
let class = class_literal.apply_optional_specialization(db, specialization);
|
||||
|
||||
let original_bases = class_literal.explicit_bases(db);
|
||||
|
||||
match original_bases {
|
||||
// `builtins.object` is the special case:
|
||||
// the only class in Python that has an MRO with length <2
|
||||
@@ -156,18 +139,20 @@ impl<'db> Mro<'db> {
|
||||
)
|
||||
) =>
|
||||
{
|
||||
ClassBase::try_from_type(db, *single_base, class.class_literal(db).0).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
Err(MroErrorKind::InheritanceCycle)
|
||||
} else {
|
||||
Ok(std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db, specialization))
|
||||
.collect())
|
||||
}
|
||||
},
|
||||
)
|
||||
ClassBase::try_from_type(db, *single_base, ClassLiteral::Static(class_literal))
|
||||
.map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
Err(MroErrorKind::InheritanceCycle)
|
||||
} else {
|
||||
Ok(std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db, specialization))
|
||||
.collect())
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|err| err.into_mro_error(db, class))
|
||||
}
|
||||
|
||||
// The class has multiple explicit bases.
|
||||
@@ -191,7 +176,11 @@ impl<'db> Mro<'db> {
|
||||
&original_bases[i + 1..],
|
||||
);
|
||||
} else {
|
||||
match ClassBase::try_from_type(db, *base, class.class_literal(db).0) {
|
||||
match ClassBase::try_from_type(
|
||||
db,
|
||||
*base,
|
||||
ClassLiteral::Static(class_literal),
|
||||
) {
|
||||
Some(valid_base) => resolved_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
}
|
||||
@@ -199,7 +188,8 @@ impl<'db> Mro<'db> {
|
||||
}
|
||||
|
||||
if !invalid_bases.is_empty() {
|
||||
return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice()));
|
||||
return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice())
|
||||
.into_mro_error(db, class));
|
||||
}
|
||||
|
||||
// `Generic` is implicitly added to the bases list of a class that has PEP-695 type parameters
|
||||
@@ -211,7 +201,7 @@ impl<'db> Mro<'db> {
|
||||
let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])];
|
||||
for base in &resolved_bases {
|
||||
if base.has_cyclic_mro(db) {
|
||||
return Err(MroErrorKind::InheritanceCycle);
|
||||
return Err(MroErrorKind::InheritanceCycle.into_mro_error(db, class));
|
||||
}
|
||||
seqs.push(base.mro(db, specialization).collect());
|
||||
}
|
||||
@@ -239,7 +229,9 @@ impl<'db> Mro<'db> {
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(MroErrorKind::Pep695ClassWithGenericInheritance);
|
||||
return Err(
|
||||
MroErrorKind::Pep695ClassWithGenericInheritance.into_mro_error(db, class)
|
||||
);
|
||||
}
|
||||
|
||||
let mut duplicate_dynamic_bases = false;
|
||||
@@ -258,9 +250,11 @@ impl<'db> Mro<'db> {
|
||||
// `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as
|
||||
// precise!).
|
||||
for (index, base) in original_bases.iter().enumerate() {
|
||||
let Some(base) =
|
||||
ClassBase::try_from_type(db, *base, class.class_literal(db).0)
|
||||
else {
|
||||
let Some(base) = ClassBase::try_from_type(
|
||||
db,
|
||||
*base,
|
||||
ClassLiteral::Static(class_literal),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
base_to_indices.entry(base).or_default().push(index);
|
||||
@@ -299,16 +293,118 @@ impl<'db> Mro<'db> {
|
||||
} else {
|
||||
Err(MroErrorKind::UnresolvableMro {
|
||||
bases_list: original_bases.iter().copied().collect(),
|
||||
})
|
||||
}
|
||||
.into_mro_error(db, class))
|
||||
}
|
||||
} else {
|
||||
Err(MroErrorKind::DuplicateBases(
|
||||
duplicate_bases.into_boxed_slice(),
|
||||
))
|
||||
Err(
|
||||
MroErrorKind::DuplicateBases(duplicate_bases.into_boxed_slice())
|
||||
.into_mro_error(db, class),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
Self::from([
|
||||
ClassBase::Class(class),
|
||||
ClassBase::unknown(),
|
||||
ClassBase::object(db),
|
||||
])
|
||||
}
|
||||
|
||||
/// Attempt to resolve the MRO of a dynamic class (created via `type(name, bases, dict)`).
|
||||
///
|
||||
/// Uses C3 linearization when possible, returning an error if the MRO cannot be resolved.
|
||||
pub(super) fn of_dynamic_class(
|
||||
db: &'db dyn Db,
|
||||
dynamic: DynamicClassLiteral<'db>,
|
||||
) -> Result<Self, DynamicMroError<'db>> {
|
||||
let bases = dynamic.bases(db);
|
||||
|
||||
// Check for duplicate bases first, but skip dynamic bases like `Unknown` or `Any`.
|
||||
let mut seen = FxHashSet::default();
|
||||
let mut duplicates = Vec::new();
|
||||
for base in bases {
|
||||
if matches!(base, ClassBase::Dynamic(_)) {
|
||||
continue;
|
||||
}
|
||||
if !seen.insert(*base) {
|
||||
duplicates.push(*base);
|
||||
}
|
||||
}
|
||||
if !duplicates.is_empty() {
|
||||
return Err(
|
||||
DynamicMroErrorKind::DuplicateBases(duplicates.into_boxed_slice())
|
||||
.into_error(db, dynamic),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if any bases are dynamic, like `Unknown` or `Any`.
|
||||
let has_dynamic_bases = bases
|
||||
.iter()
|
||||
.any(|base| matches!(base, ClassBase::Dynamic(_)));
|
||||
|
||||
// Compute MRO using C3 linearization.
|
||||
let mro_bases = if bases.is_empty() {
|
||||
// Empty bases: MRO is just `object`.
|
||||
Some(vec![ClassBase::object(db)])
|
||||
} else if bases.len() == 1 {
|
||||
// Single base: MRO is just that base's MRO.
|
||||
Some(bases[0].mro(db, None).collect())
|
||||
} else {
|
||||
// Multiple bases: use C3 merge algorithm.
|
||||
let mut seqs: Vec<VecDeque<ClassBase<'db>>> = Vec::with_capacity(bases.len() + 1);
|
||||
|
||||
// Add each base's MRO.
|
||||
for base in bases {
|
||||
seqs.push(base.mro(db, None).collect());
|
||||
}
|
||||
|
||||
// Add the list of bases in order.
|
||||
seqs.push(bases.iter().copied().collect());
|
||||
|
||||
c3_merge(seqs).map(|mro| mro.iter().copied().collect())
|
||||
};
|
||||
|
||||
match mro_bases {
|
||||
Some(mro) => {
|
||||
let mut result = vec![ClassBase::Class(ClassType::NonGeneric(dynamic.into()))];
|
||||
result.extend(mro);
|
||||
Ok(Self::from(result))
|
||||
}
|
||||
None => {
|
||||
// C3 merge failed. If there are dynamic bases, use the fallback MRO.
|
||||
// Otherwise, report an error.
|
||||
if has_dynamic_bases {
|
||||
Ok(Self::dynamic_fallback(db, dynamic))
|
||||
} else {
|
||||
Err(DynamicMroErrorKind::UnresolvableMro.into_error(db, dynamic))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a fallback MRO for a dynamic class when `of_dynamic_class` fails.
|
||||
///
|
||||
/// Iterates over base MROs sequentially with deduplication.
|
||||
pub(super) fn dynamic_fallback(db: &'db dyn Db, dynamic: DynamicClassLiteral<'db>) -> Self {
|
||||
let self_base = ClassBase::Class(ClassType::NonGeneric(dynamic.into()));
|
||||
let mut result = vec![self_base];
|
||||
let mut seen = FxHashSet::default();
|
||||
seen.insert(self_base);
|
||||
|
||||
for base in dynamic.bases(db) {
|
||||
for item in base.mro(db, None) {
|
||||
if seen.insert(item) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::from(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db, const N: usize> From<[ClassBase<'db>; N]> for Mro<'db> {
|
||||
@@ -354,8 +450,8 @@ impl<'db> FromIterator<ClassBase<'db>> for Mro<'db> {
|
||||
///
|
||||
/// Even for first-party code, where we will have to resolve the MRO for every class we encounter,
|
||||
/// loading the cached MRO comes with a certain amount of overhead, so it's best to avoid calling the
|
||||
/// Salsa-tracked [`ClassLiteral::try_mro`] method unless it's absolutely necessary.
|
||||
pub(super) struct MroIterator<'db> {
|
||||
/// Salsa-tracked [`StaticClassLiteral::try_mro`] method unless it's absolutely necessary.
|
||||
pub(crate) struct MroIterator<'db> {
|
||||
db: &'db dyn Db,
|
||||
|
||||
/// The class whose MRO we're iterating over
|
||||
@@ -390,19 +486,39 @@ impl<'db> MroIterator<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn first_element(&self) -> ClassBase<'db> {
|
||||
match self.class {
|
||||
ClassLiteral::Static(literal) => ClassBase::Class(
|
||||
literal.apply_optional_specialization(self.db, self.specialization),
|
||||
),
|
||||
ClassLiteral::Dynamic(literal) => {
|
||||
ClassBase::Class(ClassType::NonGeneric(literal.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize the full MRO of the class.
|
||||
/// Return an iterator over that MRO which skips the first element of the MRO.
|
||||
fn full_mro_except_first_element(&mut self) -> impl Iterator<Item = ClassBase<'db>> + '_ {
|
||||
fn full_mro_except_first_element(&mut self) -> &mut std::slice::Iter<'db, ClassBase<'db>> {
|
||||
self.subsequent_elements
|
||||
.get_or_insert_with(|| {
|
||||
let mut full_mro_iter = match self.class.try_mro(self.db, self.specialization) {
|
||||
Ok(mro) => mro.iter(),
|
||||
Err(error) => error.fallback_mro().iter(),
|
||||
};
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
.get_or_insert_with(|| match self.class {
|
||||
ClassLiteral::Static(literal) => {
|
||||
let mut full_mro_iter = match literal.try_mro(self.db, self.specialization) {
|
||||
Ok(mro) => mro.iter(),
|
||||
Err(error) => error.fallback_mro().iter(),
|
||||
};
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
}
|
||||
ClassLiteral::Dynamic(literal) => {
|
||||
let mut full_mro_iter = match literal.try_mro(self.db) {
|
||||
Ok(mro) => mro.iter(),
|
||||
Err(error) => error.fallback_mro().iter(),
|
||||
};
|
||||
full_mro_iter.next();
|
||||
full_mro_iter
|
||||
}
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,12 +528,9 @@ impl<'db> Iterator for MroIterator<'db> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.first_element_yielded {
|
||||
self.first_element_yielded = true;
|
||||
return Some(ClassBase::Class(
|
||||
self.class
|
||||
.apply_optional_specialization(self.db, self.specialization),
|
||||
));
|
||||
return Some(self.first_element());
|
||||
}
|
||||
self.full_mro_except_first_element().next()
|
||||
self.full_mro_except_first_element().next().copied()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,3 +657,49 @@ fn c3_merge(mut sequences: Vec<VecDeque<ClassBase>>) -> Option<Mro> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for dynamic class MRO computation with fallback MRO.
|
||||
///
|
||||
/// Separate from [`MroError`] because dynamic classes can only have a subset of MRO errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::Update)]
|
||||
pub(crate) struct DynamicMroError<'db> {
|
||||
kind: DynamicMroErrorKind<'db>,
|
||||
fallback_mro: Mro<'db>,
|
||||
}
|
||||
|
||||
impl<'db> DynamicMroError<'db> {
|
||||
/// Return the error kind describing why we could not resolve the MRO.
|
||||
pub(crate) fn reason(&self) -> &DynamicMroErrorKind<'db> {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
/// Return the fallback MRO to use for type inference.
|
||||
pub(crate) fn fallback_mro(&self) -> &Mro<'db> {
|
||||
&self.fallback_mro
|
||||
}
|
||||
}
|
||||
|
||||
/// Error kinds for dynamic class MRO computation.
|
||||
///
|
||||
/// These mirror the relevant variants from `MroErrorKind` for static classes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::Update)]
|
||||
pub(crate) enum DynamicMroErrorKind<'db> {
|
||||
/// The class has duplicate bases in its bases tuple.
|
||||
DuplicateBases(Box<[ClassBase<'db>]>),
|
||||
|
||||
/// The MRO is unresolvable through the C3-merge algorithm.
|
||||
UnresolvableMro,
|
||||
}
|
||||
|
||||
impl<'db> DynamicMroErrorKind<'db> {
|
||||
fn into_error(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
class_literal: DynamicClassLiteral<'db>,
|
||||
) -> DynamicMroError<'db> {
|
||||
DynamicMroError {
|
||||
kind: self,
|
||||
fallback_mro: Mro::dynamic_fallback(db, class_literal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl ClassInfoConstraintFunction {
|
||||
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
||||
let constraint_fn = |class: ClassLiteral<'db>| match self {
|
||||
let constraint_from_class_literal = |class: ClassLiteral<'db>| match self {
|
||||
ClassInfoConstraintFunction::IsInstance => {
|
||||
Type::instance(db, class.top_materialization(db))
|
||||
}
|
||||
@@ -166,9 +166,11 @@ impl ClassInfoConstraintFunction {
|
||||
|
||||
match classinfo {
|
||||
Type::TypeAlias(alias) => self.generate_constraint(db, alias.value_type(db)),
|
||||
Type::ClassLiteral(class_literal) => Some(constraint_fn(class_literal)),
|
||||
Type::ClassLiteral(class_literal) => Some(constraint_from_class_literal(class_literal)),
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class)) => Some(constraint_fn(class)),
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class_literal)) => {
|
||||
Some(constraint_from_class_literal(class_literal))
|
||||
}
|
||||
// It's not valid to use a generic alias as the second argument to `isinstance()` or `issubclass()`,
|
||||
// e.g. `isinstance(x, list[int])` fails at runtime.
|
||||
SubclassOfInner::Class(ClassType::Generic(_)) => None,
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
symbol::ScopedSymbolId, use_def_map,
|
||||
},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, ClassType, KnownClass, Type,
|
||||
ClassBase, ClassType, KnownClass, StaticClassLiteral, Type,
|
||||
class::CodeGeneratorKind,
|
||||
context::InferContext,
|
||||
diagnostic::{
|
||||
@@ -45,7 +45,10 @@ const PROHIBITED_NAMEDTUPLE_ATTRS: &[&str] = &[
|
||||
"_source",
|
||||
];
|
||||
|
||||
pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: ClassLiteral<'db>) {
|
||||
// TODO: Support dynamic class literals. If we allow dynamic classes to define attributes in their
|
||||
// namespace dictionary, we should also check whether those attributes are valid overrides of
|
||||
// attributes in their superclasses.
|
||||
pub(super) fn check_class<'db>(context: &InferContext<'db, '_>, class: StaticClassLiteral<'db>) {
|
||||
let db = context.db();
|
||||
let configuration = OverrideRulesConfig::from(context);
|
||||
if configuration.no_rules_enabled() {
|
||||
@@ -118,8 +121,10 @@ fn check_class_declaration<'db>(
|
||||
return;
|
||||
};
|
||||
|
||||
let (literal, specialization) = class.class_literal(db);
|
||||
let class_kind = CodeGeneratorKind::from_class(db, literal, specialization);
|
||||
let Some((literal, specialization)) = class.static_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let class_kind = CodeGeneratorKind::from_class(db, literal.into(), specialization);
|
||||
|
||||
// Check for prohibited `NamedTuple` attribute overrides.
|
||||
//
|
||||
@@ -171,7 +176,11 @@ fn check_class_declaration<'db>(
|
||||
ClassBase::Class(class) => class,
|
||||
};
|
||||
|
||||
let (superclass_literal, superclass_specialization) = superclass.class_literal(db);
|
||||
let Some((superclass_literal, superclass_specialization)) =
|
||||
superclass.static_class_literal(db)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
let superclass_symbol_table = place_table(db, superclass_scope);
|
||||
let superclass_symbol_id = superclass_symbol_table.symbol_id(&member.name);
|
||||
@@ -191,10 +200,13 @@ fn check_class_declaration<'db>(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
method_kind =
|
||||
CodeGeneratorKind::from_class(db, superclass_literal, superclass_specialization)
|
||||
.map(MethodKind::Synthesized)
|
||||
.unwrap_or_default();
|
||||
method_kind = CodeGeneratorKind::from_class(
|
||||
db,
|
||||
superclass_literal.into(),
|
||||
superclass_specialization,
|
||||
)
|
||||
.map(MethodKind::Synthesized)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
let Place::Defined(DefinedPlace {
|
||||
|
||||
@@ -16,9 +16,9 @@ use crate::{
|
||||
},
|
||||
semantic_index::{definition::Definition, place::ScopedPlaceId, place_table, use_def_map},
|
||||
types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral,
|
||||
ClassType, FindLegacyTypeVarsVisitor, InstanceFallbackShadowsNonDataDescriptor,
|
||||
KnownFunction, MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, CallableType, ClassBase, ClassType,
|
||||
FindLegacyTypeVarsVisitor, InstanceFallbackShadowsNonDataDescriptor, KnownFunction,
|
||||
MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature, StaticClassLiteral,
|
||||
Type, TypeMapping, TypeQualifiers, TypeVarVariance, VarianceInferable,
|
||||
constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension},
|
||||
context::InferContext,
|
||||
@@ -29,11 +29,11 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
impl<'db> ClassLiteral<'db> {
|
||||
impl<'db> StaticClassLiteral<'db> {
|
||||
/// Returns `Some` if this is a protocol class, `None` otherwise.
|
||||
pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option<ProtocolClass<'db>> {
|
||||
self.is_protocol(db)
|
||||
.then_some(ProtocolClass(ClassType::NonGeneric(self)))
|
||||
.then_some(ProtocolClass(ClassType::NonGeneric(self.into())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +76,12 @@ impl<'db> ProtocolClass<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn is_runtime_checkable(self, db: &'db dyn Db) -> bool {
|
||||
self.class_literal(db)
|
||||
.0
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
self.static_class_literal(db)
|
||||
.is_some_and(|(class_literal, _)| {
|
||||
class_literal
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the body of the protocol class. Check that all definitions
|
||||
@@ -88,7 +90,10 @@ impl<'db> ProtocolClass<'db> {
|
||||
pub(super) fn validate_members(self, context: &InferContext) {
|
||||
let db = context.db();
|
||||
let interface = self.interface(db);
|
||||
let body_scope = self.class_literal(db).0.body_scope(db);
|
||||
let Some((class_literal, _)) = self.static_class_literal(db) else {
|
||||
return;
|
||||
};
|
||||
let body_scope = class_literal.body_scope(db);
|
||||
let class_place_table = place_table(db, body_scope);
|
||||
|
||||
for (symbol_id, mut bindings_iterator) in
|
||||
@@ -104,7 +109,11 @@ impl<'db> ProtocolClass<'db> {
|
||||
self.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|superclass| {
|
||||
let superclass_scope = superclass.class_literal(db).0.body_scope(db);
|
||||
let Some((superclass_literal, _)) = superclass.static_class_literal(db)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let superclass_scope = superclass_literal.body_scope(db);
|
||||
let Some(scoped_symbol_id) =
|
||||
place_table(db, superclass_scope).symbol_id(symbol_name)
|
||||
else {
|
||||
@@ -879,7 +888,7 @@ impl BoundOnClass {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner Salsa query for [`ProtocolClassLiteral::interface`].
|
||||
/// Inner Salsa query for [`ProtocolClass::interface`].
|
||||
#[salsa::tracked(cycle_initial=proto_interface_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn cached_protocol_interface<'db>(
|
||||
db: &'db dyn Db,
|
||||
@@ -887,15 +896,16 @@ fn cached_protocol_interface<'db>(
|
||||
) -> ProtocolInterface<'db> {
|
||||
let mut members = BTreeMap::default();
|
||||
|
||||
for (parent_protocol, specialization) in class
|
||||
for (parent_scope, specialization) in class
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter_map(|class| {
|
||||
let (class, specialization) = class.class_literal(db);
|
||||
Some((class.into_protocol_class(db)?, specialization))
|
||||
let (class_literal, specialization) = class.static_class_literal(db)?;
|
||||
let protocol_class = class_literal.into_protocol_class(db)?;
|
||||
let parent_scope = protocol_class.static_class_literal(db)?.0.body_scope(db);
|
||||
Some((parent_scope, specialization))
|
||||
})
|
||||
{
|
||||
let parent_scope = parent_protocol.class_literal(db).0.body_scope(db);
|
||||
let use_def_map = use_def_map(db, parent_scope);
|
||||
let place_table = place_table(db, parent_scope);
|
||||
let mut direct_members = FxHashMap::default();
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::types::protocol_class::ProtocolClass;
|
||||
use crate::types::relation::{HasRelationToVisitor, IsDisjointVisitor, TypeRelation};
|
||||
use crate::types::variance::VarianceInferable;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
|
||||
FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, MemberLookupPolicy,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, ClassType, DynamicClassLiteral,
|
||||
DynamicType, FindLegacyTypeVarsVisitor, KnownClass, MaterializationKind, MemberLookupPolicy,
|
||||
NormalizedVisitor, SpecialFormType, Type, TypeContext, TypeMapping, TypeVarBoundOrConstraints,
|
||||
TypedDictType, UnionType, todo_type,
|
||||
};
|
||||
@@ -334,7 +334,7 @@ impl<'db> SubclassOfType<'db> {
|
||||
pub(crate) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
|
||||
self.subclass_of
|
||||
.into_class(db)
|
||||
.is_some_and(|class| class.class_literal(db).0.is_typed_dict(db))
|
||||
.is_some_and(|class| class.class_literal(db).is_typed_dict(db))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,3 +534,9 @@ impl<'db> From<SubclassOfType<'db>> for Type<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<DynamicClassLiteral<'db>> for SubclassOfInner<'db> {
|
||||
fn from(value: DynamicClassLiteral<'db>) -> Self {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(ClassLiteral::Dynamic(value)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,6 +1313,14 @@ pub enum Tuple<T> {
|
||||
}
|
||||
|
||||
impl<T> Tuple<T> {
|
||||
/// Returns the inner fixed-length tuple if this is a `Tuple::Fixed` variant.
|
||||
pub(crate) fn as_fixed_length(&self) -> Option<&FixedLengthTuple<T>> {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => Some(tuple),
|
||||
Tuple::Variable(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn homogeneous(element: T) -> Self {
|
||||
Self::Variable(VariableLengthTuple::homogeneous(element))
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use super::diagnostic::{
|
||||
use super::{ApplyTypeMappingVisitor, Type, TypeMapping, visitor};
|
||||
use crate::Db;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::TypeDefinition;
|
||||
use crate::types::class::FieldKind;
|
||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||
use crate::types::generics::InferableTypeVars;
|
||||
@@ -80,7 +81,9 @@ impl<'db> TypedDictType<'db> {
|
||||
pub(crate) fn items(self, db: &'db dyn Db) -> &'db TypedDictSchema<'db> {
|
||||
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
|
||||
fn class_based_items<'db>(db: &'db dyn Db, class: ClassType<'db>) -> TypedDictSchema<'db> {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
let Some((class_literal, specialization)) = class.static_class_literal(db) else {
|
||||
return TypedDictSchema::default();
|
||||
};
|
||||
class_literal
|
||||
.fields(db, specialization, CodeGeneratorKind::TypedDict)
|
||||
.into_iter()
|
||||
@@ -305,6 +308,13 @@ impl<'db> TypedDictType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_definition(self, db: &'db dyn Db) -> Option<TypeDefinition<'db>> {
|
||||
match self {
|
||||
TypedDictType::Class(defining_class) => Some(defining_class.type_definition(db)),
|
||||
TypedDictType::Synthesized(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
match self {
|
||||
TypedDictType::Class(_) => {
|
||||
|
||||
@@ -200,13 +200,28 @@ impl UnmatchedWithColumn for &Diagnostic {
|
||||
|
||||
/// Discard `@Todo`-type metadata from expected types, which is not available
|
||||
/// when running in release mode.
|
||||
///
|
||||
/// Some `@Todo` variants (like `@Todo(StarredExpression)` and `@Todo(typing.Unpack)`)
|
||||
/// are hardcoded enum variants that always display their message, so we preserve those.
|
||||
fn discard_todo_metadata(ty: &str) -> Cow<'_, str> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
/// `@Todo` variants that are hardcoded and always display their message,
|
||||
/// even in release mode.
|
||||
const PRESERVED_TODO_VARIANTS: &[&str] =
|
||||
&["@Todo(StarredExpression)", "@Todo(typing.Unpack)"];
|
||||
|
||||
static TODO_METADATA_REGEX: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"@Todo\([^)]*\)").unwrap());
|
||||
|
||||
TODO_METADATA_REGEX.replace_all(ty, "@Todo")
|
||||
TODO_METADATA_REGEX.replace_all(ty, |caps: ®ex::Captures| {
|
||||
let matched = caps.get(0).unwrap().as_str();
|
||||
if PRESERVED_TODO_VARIANTS.contains(&matched) {
|
||||
matched.to_string()
|
||||
} else {
|
||||
"@Todo".to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
10
ty.schema.json
generated
10
ty.schema.json
generated
@@ -1160,6 +1160,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported-dynamic-base": {
|
||||
"title": "detects dynamic class bases that are unsupported as ty could not feasibly calculate the class's MRO",
|
||||
"description": "## What it does\nChecks for dynamic class definitions (using `type()`) that have bases\nwhich are unsupported by ty.\n\nThis is equivalent to [`unsupported-base`] but applies to classes created\nvia `type()` rather than `class` statements.\n\n## Why is this bad?\nIf a dynamically created class has a base that is an unsupported type\nsuch as `type[T]`, ty will not be able to resolve the\n[method resolution order] (MRO) for the class. This may lead to an inferior\nunderstanding of your codebase and unpredictable type-checking behavior.\n\n## Default level\nThis rule is disabled by default because it will not cause a runtime error,\nand may be noisy on codebases that use `type()` in highly dynamic ways.\n\n## Examples\n```python\ndef factory(base: type[Base]) -> type:\n # `base` has type `type[Base]`, not `type[Base]` itself\n return type(\"Dynamic\", (base,), {}) # error: [unsupported-dynamic-base]\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order\n[`unsupported-base`]: https://docs.astral.sh/ty/rules/unsupported-base",
|
||||
"default": "ignore",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported-operator": {
|
||||
"title": "detects binary, unary, or comparison expressions where the operands don't support the operator",
|
||||
"description": "## What it does\nChecks for binary expressions, comparisons, and unary expressions where\nthe operands don't support the operator.\n\n## Why is this bad?\nAttempting to use an unsupported operator will raise a `TypeError` at\nruntime.\n\n## Examples\n```python\nclass A: ...\n\nA() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'\n```",
|
||||
|
||||
Reference in New Issue
Block a user