Skip to content

Commit 91055c7

Browse files
authored
Refine the copy._SupportsReplace.__replace__ signature (#14786)
1 parent 250bf77 commit 91055c7

File tree

2 files changed

+24
-6
lines changed

2 files changed

+24
-6
lines changed

stdlib/@tests/test_cases/check_copy.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import copy
44
import sys
5+
from typing import Generic, TypeVar
56
from typing_extensions import Self, assert_type
67

78

@@ -19,3 +20,20 @@ def __replace__(self, val: int) -> Self:
1920
obj = ReplaceableClass(42)
2021
cpy = copy.replace(obj, val=23)
2122
assert_type(cpy, ReplaceableClass)
23+
24+
25+
_T_co = TypeVar("_T_co", covariant=True)
26+
27+
28+
class Box(Generic[_T_co]):
29+
def __init__(self, value: _T_co, /) -> None:
30+
self.value = value
31+
32+
def __replace__(self, value: str) -> Box[str]:
33+
return Box(value)
34+
35+
36+
if sys.version_info >= (3, 13):
37+
box1: Box[int] = Box(42)
38+
box2 = copy.replace(box1, val="spam")
39+
assert_type(box2, Box[str])

stdlib/copy.pyi

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import sys
22
from typing import Any, Protocol, TypeVar, type_check_only
3-
from typing_extensions import Self
43

54
__all__ = ["Error", "copy", "deepcopy"]
65

76
_T = TypeVar("_T")
8-
_SR = TypeVar("_SR", bound=_SupportsReplace)
7+
_RT_co = TypeVar("_RT_co", covariant=True)
98

109
@type_check_only
11-
class _SupportsReplace(Protocol):
12-
# In reality doesn't support args, but there's no other great way to express this.
13-
def __replace__(self, *args: Any, **kwargs: Any) -> Self: ...
10+
class _SupportsReplace(Protocol[_RT_co]):
11+
# In reality doesn't support args, but there's no great way to express this.
12+
def __replace__(self, /, *_: Any, **changes: Any) -> _RT_co: ...
1413

1514
# None in CPython but non-None in Jython
1615
PyStringMap: Any
@@ -21,7 +20,8 @@ def copy(x: _T) -> _T: ...
2120

2221
if sys.version_info >= (3, 13):
2322
__all__ += ["replace"]
24-
def replace(obj: _SR, /, **changes: Any) -> _SR: ...
23+
# The types accepted by `**changes` match those of `obj.__replace__`.
24+
def replace(obj: _SupportsReplace[_RT_co], /, **changes: Any) -> _RT_co: ...
2525

2626
class Error(Exception): ...
2727

0 commit comments

Comments
 (0)