Skip to content
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

mypy 0.990 breaks commonly used Protocol definition for dataclasses #14029

Closed
intgr opened this issue Nov 7, 2022 · 5 comments
Closed

mypy 0.990 breaks commonly used Protocol definition for dataclasses #14029

intgr opened this issue Nov 7, 2022 · 5 comments
Assignees
Labels
bug mypy got something wrong topic-dataclasses

Comments

@intgr
Copy link
Contributor

intgr commented Nov 7, 2022

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.

@intgr
Copy link
Contributor Author

intgr commented Nov 7, 2022

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

@hauntsaninja
Copy link
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.

@oxan
Copy link

oxan commented Nov 7, 2022

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.

@hauntsaninja
Copy link
Collaborator

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

@oxan
Copy link

oxan commented Nov 7, 2022

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
Labels
bug mypy got something wrong topic-dataclasses
Projects
None yet
Development

No branches or pull requests

4 participants