Description
Bug Report
The mypy attrs plugin has custom support for attrs.evolve
, which has different code branches for regular and generic types (starting here).
It admits attrs.has
as a type guard for attrs.evolve
if the original type is Any
, but not if it's a generic type parameter T
with no type bound.
To Reproduce
On python 3.12 (so lacking copy.replace
):
def replace[T](value: T, **kwargs: Any) -> T:
if attrs.has(type(value)):
return attrs.evolve(value, **kwargs)
else:
raise NotImplementedError(f"replace is not implemented for {type(value)}")
Mypy reports, error: Argument 1 to "evolve" has a variable type "T" not bound to an attrs class [misc]
(In the actual code I'd do something else in other branches; this is intended as a replacement for copy.replace
on python < 3.13, supporting different types besides attrs classes.)
Using the attr.AttrInstance
protocol also doesn't work, which means I can't define my own type guard wrapping attrs.has
. This code results in the same mypy error:
def replace[T: attrs.AttrsInstance](value: T, **kwargs: Any) -> T:
return attrs.evolve(value, **kwargs)
However, this code works (and is a viable workaround):
def replace[T](value: T, **kwargs: Any) -> T:
val2: Any = value
if attrs.has(type(val2)):
return attrs.evolve(val2, **kwargs)
else:
raise NotImplementedError(f"replace is not implemented for {type(value)}")
Expected Behavior
Mypy should honor attrs.has
as a type guard for attrs.evolve
, whether the original type is generic or any other kind.
- Mypy version used: 1.15.0
- Python version used: 3.12.10