mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Conditionally defined dataclass fields (#19197)
## Summary Fixes a bug where conditionally defined dataclass fields were previously ignored. Thanks to @lipefree for reporting this. ## Test Plan New Markdown tests
This commit is contained in:
parent
d78d10dd94
commit
ce2bdb9357
3 changed files with 91 additions and 1 deletions
|
@ -558,6 +558,50 @@ class C(Base):
|
||||||
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
|
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Conditionally defined fields
|
||||||
|
|
||||||
|
### Statically known conditions
|
||||||
|
|
||||||
|
Fields that are defined in always-reachable branches are always present in the synthesized
|
||||||
|
`__init__` method. Fields that are defined in never-reachable branches are not present:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
normal: int
|
||||||
|
|
||||||
|
if 1 + 2 == 3:
|
||||||
|
always_present: str
|
||||||
|
|
||||||
|
if 1 + 2 == 4:
|
||||||
|
never_present: bool
|
||||||
|
|
||||||
|
reveal_type(C.__init__) # revealed: (self: C, normal: int, always_present: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic conditions
|
||||||
|
|
||||||
|
If a field is conditionally defined, we currently assume that it is always present. A more complex
|
||||||
|
alternative here would be to synthesized a union of all possible `__init__` signatures:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
def flag() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
normal: int
|
||||||
|
|
||||||
|
if flag():
|
||||||
|
conditionally_present: str
|
||||||
|
|
||||||
|
reveal_type(C.__init__) # revealed: (self: C, normal: int, conditionally_present: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
## Generic dataclasses
|
## Generic dataclasses
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -789,6 +833,23 @@ class Fails: # error: [duplicate-kw-only]
|
||||||
reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This also works if `KW_ONLY` is used in a conditional branch:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def flag() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class D: # error: [duplicate-kw-only]
|
||||||
|
x: int
|
||||||
|
_1: KW_ONLY
|
||||||
|
|
||||||
|
if flag():
|
||||||
|
y: str
|
||||||
|
_2: KW_ONLY
|
||||||
|
z: float
|
||||||
|
```
|
||||||
|
|
||||||
## Other special cases
|
## Other special cases
|
||||||
|
|
||||||
### `dataclasses.dataclass`
|
### `dataclasses.dataclass`
|
||||||
|
|
|
@ -37,6 +37,18 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
|
||||||
23 | e: bytes
|
23 | e: bytes
|
||||||
24 |
|
24 |
|
||||||
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||||
|
26 | def flag() -> bool:
|
||||||
|
27 | return True
|
||||||
|
28 |
|
||||||
|
29 | @dataclass
|
||||||
|
30 | class D: # error: [duplicate-kw-only]
|
||||||
|
31 | x: int
|
||||||
|
32 | _1: KW_ONLY
|
||||||
|
33 |
|
||||||
|
34 | if flag():
|
||||||
|
35 | y: str
|
||||||
|
36 | _2: KW_ONLY
|
||||||
|
37 | z: float
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
@ -109,6 +121,23 @@ info[revealed-type]: Revealed type
|
||||||
24 |
|
24 |
|
||||||
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||||
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
|
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
|
||||||
|
26 | def flag() -> bool:
|
||||||
|
27 | return True
|
||||||
|
|
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
|
||||||
|
--> src/mdtest_snippet.py:30:7
|
||||||
|
|
|
||||||
|
29 | @dataclass
|
||||||
|
30 | class D: # error: [duplicate-kw-only]
|
||||||
|
| ^
|
||||||
|
31 | x: int
|
||||||
|
32 | _1: KW_ONLY
|
||||||
|
|
|
||||||
|
info: `KW_ONLY` fields: `_1`, `_2`
|
||||||
|
info: rule `duplicate-kw-only` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
@ -1650,7 +1650,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
if !declarations
|
if !declarations
|
||||||
.clone()
|
.clone()
|
||||||
.all(|DeclarationWithConstraint { declaration, .. }| {
|
.all(|DeclarationWithConstraint { declaration, .. }| {
|
||||||
declaration.is_defined_and(|declaration| {
|
declaration.is_undefined_or(|declaration| {
|
||||||
matches!(
|
matches!(
|
||||||
declaration.kind(db),
|
declaration.kind(db),
|
||||||
DefinitionKind::AnnotatedAssignment(..)
|
DefinitionKind::AnnotatedAssignment(..)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue