Skip to content

attrs plugin does not support generic type #18973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
danarmak opened this issue Apr 26, 2025 · 1 comment
Open

attrs plugin does not support generic type #18973

danarmak opened this issue Apr 26, 2025 · 1 comment
Labels
bug mypy got something wrong topic-attrs

Comments

@danarmak
Copy link

danarmak commented Apr 26, 2025

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
@danarmak danarmak added the bug mypy got something wrong label Apr 26, 2025
@danarmak
Copy link
Author

Perhaps relatedly, the attrs project used to have a TypeGuard[Type[attrs.AttrsInstance]] declaration on attrs.has but removed it at some point, so this doesn't work 'out of the box' as normal code. Presumably the mypy attrs plugin would override that type guard, but do all type checkers have custom attrs support? Why didn't a normal TypeGuard and protocol work (for evolve in particular, not for all of the attrs support)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-attrs
Projects
None yet
Development

No branches or pull requests

2 participants