python / python/mypy

attrs plugin does not support generic type

Open
#18,973 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-attrs
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in mypy/plugins/attrs.py around the attrs.evolve handling at line 914, then run the provided Python 3.12 generic example against mypy 1.15.0 to observe the diagnostic. Trace how attrs.has is recognized for Any versus an unbound type parameter, and confirm completion when the generic example is accepted without the variable-type error while preserving the expected return type.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.