Skip to content

[copy] Accept unions of dataclasses in copy.replace via overload - #16733

Open
hrithiksaini99 wants to merge 3 commits into
python:mainfrom
hrithiksaini99:fix/16582-copy-replace-union
Open

hrithiksaini99 wants to merge 3 commits into
python:mainfrom
hrithiksaini99:fix/16582-copy-replace-union

Conversation

@hrithiksaini99

Copy link
Copy Markdown
Contributor

Problem

Since #14819, copy.replace is typed through _SupportsReplace[_P, _RT_co] with a ParamSpec, and a union argument whose members each have a field the others lack (e.g. Circle | Square sharing only name) is rejected — even though the call is fine at runtime (verified: copy.replace(Square('s', 1.0), name='renamed') returns Square(name='renamed', side=1.0)). A ParamSpec cannot bind a union of signatures.

Fix

Add the second overload from the issue:

@final
@type_check_only
class _Unreplaceable: ...

@overload
def replace(obj: _SupportsReplace[_P, _RT_co], /, *_: _P.args, **changes: _P.kwargs) -> _RT_co: ...
@overload
def replace(obj: _Unreplaceable, /, **changes: object) -> Never: ...

No real argument matches _Unreplaceable; its presence makes checkers fall back to argument-type expansion (typing spec, overload evaluation step 3) and check each union member on its own.

Verification

python tests/mypy_test.py stdlib/copy.pyi   # success on 3.10-3.15
python tests/regr_test.py stdlib            # success

New regression case: rename(Circle | Square) accepted, wrong(...) still refused.

Fixes #16582.

hrithiksaini99 and others added 3 commits November 7, 2026 13:39
Fixes python#16582. Since python#14819, copy.replace is typed via
_SupportsReplace[_P, _RT_co] with a ParamSpec, and a union argument
whose members each have a field the others lack (e.g.
Circle | Square sharing only name) is rejected: a ParamSpec cannot
bind a union of signatures.

Add the overload from the issue: a second replace() overload
taking _Unreplaceable (@Final, empty class) returning Never.
No real argument matches it; its presence makes checkers fall back
to argument-type expansion (typing spec overload evaluation step 3)
and check each union member separately.

Regression case in stdlib/@tests/test_cases/check_copy.py: rename()
accepted, wrong() still refused.

Verified: mypy_test stdlib/copy.pyi success 3.10-3.15,
regr_test stdlib success, ruff clean.
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

copy.replace rejects a union whose members each have a field the others lack (since #14819)

1 participant