dataclasses.replace() (and related, like model_copy() in pydantic, or .evolve() in attrs) copies a dataclass instance and lets you override selected fields, but the function’s current signature uses **changes: Any. As a result, static type checkers cant catch mistakes (e.g. typos in field names) and language servers can’t help with refactoring (Edit: This is technically incorrect as e.g. mypy has specialized plugins that do enable catching those errors)
One way to fix this is to introduce a type operator DataclassKwargs[T] that introspects a dataclass T and produces a TypedDict with one key per init=True field. All keys would be marked NotRequired, since when calling replace() you can provide any subset of fields:
from typing import Unpack, DataclassKwargs
def replace[T](obj: T, /, **changes: Unpack[DataclassKwargs[T]]) -> T: ...
With this signature, a type checker can verify that only valid field names and value types are supplied.
Alternatively, we could introduce AsTypedDict[T] that derives a TypedDict from a dataclass, and a generic OptionalFields[…] wrapper could mark all keys optional for “patch” objects. In that style replace() could be typed as:
from typing import Unpack, OptionalFields, AsTypedDict
def replace[T](obj: T, /, **changes: Unpack[OptionalFields[AsTypedDict[T]]]) -> T: ...
This is a bit more verbose, but AsTypedDict[T] and OptionalFields[T] could be used in other contexts.