Skip to content

Commit 168599f

Browse files
committed
[ty] synthesize __replace__ for >=3.13
1 parent c8c80e0 commit 168599f

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Replace
2+
3+
The replace function and protocol added in Python 3.13:
4+
<https://docs.python.org/3/whatsnew/3.13.html#copy>
5+
6+
## `replace()` function
7+
8+
It is present in the `copy` module.
9+
10+
```toml
11+
[environment]
12+
python-version = "3.13"
13+
```
14+
15+
```py
16+
from copy import replace
17+
```
18+
19+
## `__replace__` protocol
20+
21+
```toml
22+
[environment]
23+
python-version = "3.13"
24+
```
25+
26+
### Dataclasses
27+
28+
```py
29+
from dataclasses import dataclass
30+
from copy import replace
31+
32+
@dataclass
33+
class Point:
34+
x: int
35+
y: int
36+
37+
a = Point(1, 2)
38+
39+
# TODO: Implement __replace__ method - this should not throw an error
40+
# error: [unknown-argument]
41+
b = a.__replace__(x=3)
42+
reveal_type(b) # revealed: Point
43+
b = replace(a, x=3)
44+
reveal_type(b) # revealed: Point
45+
```
46+
47+
## Version support check
48+
49+
It is not present in Python < 3.13
50+
51+
```toml
52+
[environment]
53+
python-version = "3.12"
54+
```
55+
56+
```py
57+
from copy import replace # error: [unresolved-import]
58+
```

crates/ty_python_semantic/src/types/class.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,29 @@ impl<'db> ClassLiteral<'db> {
17351735
.place
17361736
.ignore_possibly_unbound()
17371737
}
1738+
(CodeGeneratorKind::DataclassLike, "__replace__") => {
1739+
if Program::get(db).python_version(db) < PythonVersion::PY313 {
1740+
return None;
1741+
}
1742+
1743+
Some(CallableType::function_like(
1744+
db,
1745+
Signature::new(
1746+
Parameters::new([
1747+
Parameter::positional_or_keyword(Name::new_static("self"))
1748+
.with_annotated_type(Type::instance(
1749+
db,
1750+
self.apply_optional_specialization(db, specialization),
1751+
)),
1752+
// TODO
1753+
]),
1754+
Some(Type::instance(
1755+
db,
1756+
self.apply_optional_specialization(db, specialization),
1757+
)),
1758+
),
1759+
))
1760+
}
17381761
(CodeGeneratorKind::DataclassLike, "__setattr__") => {
17391762
if has_dataclass_param(DataclassParams::FROZEN) {
17401763
let signature = Signature::new(

0 commit comments

Comments
 (0)