Skip to content

Commit a1bc0b3

Browse files
committed
add some tests
1 parent ccb1cca commit a1bc0b3

3 files changed

Lines changed: 183 additions & 12 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Tests for the `__post_init__` method
2+
3+
At runtime, if a dataclass has a `__post_init__` method then all `InitVar`-annotated fields will be
4+
passed as positional arguments to that method as the final statement in the dataclass's generated
5+
`__init__` method. The `__post_init__` signature must therefore be compatible with the fields of the
6+
dataclass:
7+
8+
```py
9+
from dataclasses import dataclass, InitVar
10+
11+
@dataclass
12+
class Empty1:
13+
def __post_init__(self): ... # fine
14+
15+
@dataclass
16+
class Empty2:
17+
def __post_init__(self) -> None: ... # fine
18+
19+
@dataclass
20+
class Empty2:
21+
# The returned value is discarded,
22+
# so arbitrary return annotations are allowed
23+
def __post_init__(self) -> int:
24+
return 42
25+
26+
@dataclass
27+
class Empty3:
28+
def __post_init__(self, *args): ... # fine
29+
30+
@dataclass
31+
class Empty4:
32+
def __post_init__(self, **kwargs): ... # fine
33+
34+
@dataclass
35+
class Empty5:
36+
def __post_init__(self, *args, **kargs): ... # fine
37+
38+
@dataclass
39+
class Empty6:
40+
# error: [invalid-dataclass]
41+
def __post_init__(self, required_argument: int): ...
42+
43+
@dataclass
44+
class Empty7:
45+
# no arguments will be passed to this method at runtime,
46+
# because there are no `InitVar` fields on the class,
47+
# so this is an error:
48+
#
49+
# error: [invalid-dataclass]
50+
def __post_init__(self, required_argument: int): ...
51+
52+
@dataclass
53+
class Empty8:
54+
# error: [invalid-dataclass]
55+
def __post_init__(self, *, required_argument): ...
56+
57+
@dataclass
58+
class Empty9:
59+
# error: [invalid-dataclass]
60+
def __post_init__(self, required_argument, /): ...
61+
62+
@dataclass
63+
class SingleField:
64+
x: int
65+
66+
# `x` will not be passed to `__post_init__`,
67+
# because it is not an `InitVar`, so this is an
68+
#
69+
# error: [invalid-dataclass]
70+
def __post_init__(self, x: int) -> None: ...
71+
72+
@dataclass
73+
class SingleFieldGood:
74+
x: int
75+
76+
# this is fine!
77+
def __post_init__(self) -> None: ...
78+
79+
@dataclass
80+
class HasInitVarNoParameter:
81+
x: InitVar[int]
82+
83+
# error: [invalid-dataclass]
84+
def __post_init__(self) -> None: ...
85+
86+
@dataclass
87+
class HasInitVarDifferentParameterName:
88+
x: InitVar[int]
89+
90+
# because arguments are always passed in positionally
91+
# to `__post_init__` methods, we allow the parameters to
92+
# have arbitrary names as long as they are annotated with
93+
# the right type. So this is fine:
94+
def __post_init__(self, xx) -> None: ...
95+
96+
@dataclass
97+
class HasInitVarBadParameterType:
98+
x: InitVar[int]
99+
100+
# error: [invalid-dataclass]
101+
def __post_init__(self, x: str) -> None: ...
102+
103+
@dataclass
104+
class HasInitVarBadParameterKind:
105+
x: InitVar[int]
106+
107+
# error: [invalid-dataclass]
108+
def __post_init__(self, *, x: int) -> None: ...
109+
110+
@dataclass
111+
class HasInitVarGood:
112+
x: InitVar[int]
113+
114+
def __post_init__(self, x: int) -> None: ...
115+
116+
@dataclass
117+
class HasInitVarGoodPositionalOnly:
118+
x: InitVar[int]
119+
120+
# arguments are always passed to `__post_init__` positionally at runtime,
121+
# so this is fine
122+
def __post_init__(self, x: int, /) -> None: ...
123+
124+
@dataclass
125+
class LotsOfInitVarsBad:
126+
a: int
127+
b: InitVar[str]
128+
c: InitVar[bytes]
129+
d: int
130+
e: int
131+
f: InitVar[range]
132+
g: int
133+
134+
# Only `InitVar` fields are passed in at runtime, so this is an
135+
# error: [invalid-dataclass]
136+
def __post_init__(self, a: int, b: str, c: bytes, d: int, e: int, f: range, g: int): ...
137+
138+
@dataclass
139+
class LotsOfInitVarsOutOfOrder:
140+
a: int
141+
b: InitVar[str]
142+
c: InitVar[bytes]
143+
d: int
144+
e: int
145+
f: InitVar[range]
146+
g: int
147+
148+
# the parameters are in the wrong order, so this is an
149+
# error: [invalid-dataclass]
150+
def __post_init__(self, c: bytes, b: str, f: range): ...
151+
152+
@dataclass
153+
class LotsOfInitVarsGood:
154+
a: int
155+
b: InitVar[str]
156+
c: InitVar[bytes]
157+
d: int
158+
e: int
159+
f: InitVar[range]
160+
g: int
161+
162+
def __post_init__(self, b: str, c: bytes, f: range): ...
163+
164+
@dataclass
165+
class InitVarSubclassGood(LotsOfInitVarsGood):
166+
h: InitVar[list[int]]
167+
i: str
168+
j: InitVar[bool]
169+
170+
def __post_init__(self, b: str, c: bytes, f: range, h: list[int], j: bool): ...
171+
172+
@dataclass
173+
class InitVarSubclassBad(LotsOfInitVarsGood):
174+
h: InitVar[list[int]]
175+
i: str
176+
j: InitVar[bool]
177+
178+
# error: [invalid-dataclass]
179+
def __post_init__(self, h: list[int], j: bool, b: str, c: bytes, f: range): ...
180+
```

crates/ty_python_semantic/resources/mdtest/liskov.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ python-version = "3.13"
411411
```
412412

413413
```pyi
414-
from dataclasses import dataclass
414+
from dataclasses import dataclass, InitVar
415415
from typing_extensions import Self
416416

417417
class Grandparent: ...
@@ -425,14 +425,14 @@ class Child(Parent):
425425

426426
@dataclass(init=False)
427427
class DataSuper:
428-
x: int
428+
x: InitVar[int]
429429

430430
def __post_init__(self, x: int) -> None:
431431
self.x = x
432432

433433
@dataclass(init=False)
434434
class DataSub(DataSuper):
435-
y: str
435+
y: InitVar[str]
436436

437437
def __post_init__(self, x: int, y: str) -> None:
438438
self.y = y

crates/ty_python_semantic/src/types.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10596,15 +10596,6 @@ impl<'db> CallableTypes<'db> {
1059610596
}
1059710597
}
1059810598

10599-
impl<'a, 'db> IntoIterator for &'a CallableTypes<'db> {
10600-
type Item = CallableType<'db>;
10601-
type IntoIter = std::iter::Copied<std::slice::Iter<'a, CallableType<'db>>>;
10602-
10603-
fn into_iter(self) -> Self::IntoIter {
10604-
self.0.iter().copied()
10605-
}
10606-
}
10607-
1060810599
/// Represents a specific instance of a bound method type for a builtin class.
1060910600
///
1061010601
/// Unlike bound methods of user-defined classes, these are not generally instances

0 commit comments

Comments
 (0)