Skip to content

Type Error with tagged Unions (working code but mypy/pyright error) #690

Closed

Description

@Gentle

functionally this code does what I expect it to (asserts go through) but there is a type error in the signature of Converter.structure, it accepts cl: type[T] and UnionType does not match that

from typing import Literal, Union
from dataclasses import dataclass
import cattrs


@dataclass
class Add:
    t: Literal["ADD"]
    v: int


@dataclass
class Sub:
    t: Literal["SUB"]
    v: int


Op = Union[Add, Sub]
Op2 = Add | Sub

assert cattrs.structure(dict(t="ADD", v=10), Op) == Add(t="ADD", v=10)
assert cattrs.structure(dict(t="SUB", v=5), Op2) == Sub(t="SUB", v=5)

if I change this https://github.com/python-attrs/cattrs/blob/main/src/cattrs/converters.py#L587 to

     def structure(self, obj: UnstructuredValue, cl: type[T] | UnionType) -> T:

the type error goes away but I'm not sure if that is the right solution

Activity

  1. Tinche commented on Oct 15, 2025

    @Tinche
    Member

    There's not really a way to improve this on our own. We need PEP 747 to be accepted and implemented: https://peps.python.org/pep-0747/

    The good news is that it's almost certainly going to be accepted, and the Mypy PR is in the final stages. I think pyright might already support it (when imported from a newer version of typing_extensions).

    I don't want to add support until the PEP is formally accepted though. It's on our immediate radar though ;)

    For now you'll have to use a # type: ignore unfortunately :/

  2. Gentle commented on Oct 15, 2025

    @Gentle
    Author

    that was quick, thanks for the link, great to know you are aware and tracking this issue :)

  3. Gentle commented on Oct 15, 2025

    @Gentle
    Author

    I just noticed that pyrefly can handle this, the type for x here is inferred as Add | Sub by the LSP

    x = cattrs.structure(dict(t="ADD", v=10), Op)
  4. linked a pull request that will close this issueSwitch to TypeForm #786on Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions