Skip to content

mypy 0.990 breaks commonly used Protocol definition for dataclasses #14029

Closed

Description

@intgr

Bug Report

With mypy 0.990 the dataclass __dataclass_fields__ attribute is now a ClassVar, which mypy considers to be incompatible with an instance attribute. This causes results like:

error: Incompatible types in assignment (expression has type "MyDataclass", variable has type "Dataclass")  [assignment]
note: Protocol member Dataclass.__dataclass_fields__ expected instance variable, got class variable

Was this change intentional, do all users of this trick have to change __dataclass_fields__: dict to ClassVar[dict], or is this an unintended result?

To Reproduce

https://mypy-play.net/?mypy=master&python=3.10&gist=c3bb7ee9862a0890e905a35dc1fe8209

class Dataclass(Protocol):
    __dataclass_fields__: dict

@dataclass
class MyDataclass:
    blah: int

x: Dataclass = MyDataclass(blah=123)
#              ^ error: Incompatible types in assignment (expression has type "MyDataclass", variable has type "Dataclass")  [assignment]
#                note: Protocol member Dataclass.__dataclass_fields__ expected instance variable, got class variable

Expected Behavior

I'm not sure to be honest whether the new behavior is correct or not, wanted to make sure.

Seems to be related to #13721, pinging @hauntsaninja.

Activity

  1. added
    bugmypy got something wrong
    on Nov 7, 2022
  2. intgr commented on Nov 7, 2022

    @intgr
    ContributorAuthor

    Another related case:

    https://mypy-play.net/?mypy=master&python=3.10&gist=04ece0675f41515ecf091ba1de914605

    class Dataclass(Protocol):
        __dataclass_fields__: dict
    
    
    T = TypeVar("T", bound=Dataclass)
    
    
    @dataclass
    class MyDataclass:
        blah: int
    
    
    #### Example 2
    class DataclassSerializer(Generic[T]):
        ...
    
    
    class MySerializer(DataclassSerializer[MyDataclass]):
        #              ^ error: Type argument "MyDataclass" of "Serializer" must be a subtype of "Dataclass"  [type-var]
        pass
  3. hauntsaninja commented on Nov 7, 2022

    @hauntsaninja
    Collaborator

    I think it's correct, and you'll need to add ClassVar. mypy recently started allowing classes as protocol implementations, which you'll probably want to distinguish. See #4536 (comment) which is the reason why I made the change.

  4. oxan commented on Nov 7, 2022

    @oxan

    I think the change makes sense and it's more logical to treat __dataclass_fields__ as a ClassVar, but it kinda sucks that previous versions of mypy don't accept a protocol with it being a classvar. That means you can be either compatible with mypy pre-0.990, or post-0.990, but not with both. For applications it's probably not a big deal, but it's not great for libraries.

  5. hauntsaninja commented on Nov 7, 2022

    @hauntsaninja
    Collaborator

    oxan, could union of two protocols work for giving you a little bit more mypy compatibility?

  6. oxan commented on Nov 7, 2022

    @oxan

    Ah yes, something like this seems to work:

    # for mypy 0.990+
    class NewStyleDataclassProtocol(Protocol):
        __dataclass_fields__: ClassVar[dict]
        
    # for mypy <0.990
    class OldStyleDataclassProtocol(Protocol):
        __dataclass_fields__: dict
        
    Dataclass = Union[OldStyleDataclassProtocol, NewStyleDataclassProtocol]

    Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions