Skip to content

Mypy 1.7.0 makes two incorrect inferences about dataclass descriptor-based fields #16538

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
jace opened this issue Nov 22, 2023 · 1 comment
Labels
bug mypy got something wrong topic-dataclasses topic-descriptors Properties, class vs. instance attributes

Comments

@jace
Copy link

jace commented Nov 22, 2023

Bug Report

When a dataclass field uses a descriptor, Mypy makes two incorrect inferences:

  1. If the descriptor has no default value (it raises AttributeError when accessed on the class), mypy incorrectly assumes it has a default and raises an error on the next field with no default value. The no-default mechanism was documented in Python 3.10 but has been there since 3.7. The specific code is in dataclasses._get_field, as default = getattr(cls, a_name, MISSING).
  2. If the descriptor-based field is typed with InitVar, mypy incorrectly assumes the attribute does not exist on the class.

To Reproduce

https://mypy-play.net/?mypy=latest&python=3.11&gist=8a47d8538f2fc798a8564d30dae6434e

Expected Behavior

No error, matching runtime behaviour and the internal validations of the @dataclass decorator.

Actual Behavior

Two incorrect errors:

  1. Attributes without a default cannot follow attributes with one [misc]
  2. "Example2" has no attribute "this_exists" [attr-defined]

Your Environment

  • Mypy version used: 1.7.0 and 1.6.1
  • Python version used: 3.9 and 3.11

Related: #13856 describes another dataclass descriptor bug, fixed in mypy 1.2.

@jace jace added the bug mypy got something wrong label Nov 22, 2023
@AlexWaygood AlexWaygood added topic-dataclasses topic-descriptors Properties, class vs. instance attributes labels Nov 22, 2023
@jace
Copy link
Author

jace commented Nov 22, 2023

I've found a workaround for the first problem using a TYPE_CHECKING test. Mypy no longer interprets this field as having a default value and will not complain about a subsequent field with no default. This only works if the descriptor's return type is known:

    if TYPE_CHECKING:
        first: str
    else:
        first: FieldDescriptor = FieldDescriptor()

It's murky when the descriptor is a generic or the type is not known. Curiously, Mypy is able to guess the actual type when the field is in a frozen dataclass and accessed from a sub-dataclass if typed like this:

    if TYPE_CHECKING:
        first: Union[FieldDescriptor, Any]
    else:
        first: FieldDescriptor = FieldDescriptor()
  1. Revealed type in the instance when it is (a) non-frozen dataclass, (b) non-frozen sub dataclass, (c) frozen dataclass: Union[__main__.FieldDescriptor, Any]

  2. Revealed type in the instance when it is (d) a frozen sub dataclass: Union[builtins.str, Any]. Here, the descriptor's type has been correctly replaced with the return type of its __get__ method

  3. Revealed type in the class for all four variants is Any

Replacing the Union with one of its two members doesn't work:

  • If typed Any, the instance field type is also Any in the frozen sub dataclass, losing the actual type
  • If typed FieldDescriptor (the descriptor type), Mypy will report an type error when the dataclass is instantiated

Code: https://mypy-play.net/?mypy=latest&python=3.11&gist=c7e30060be30861c5ec3fac61a5bfb26

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 topic-descriptors Properties, class vs. instance attributes
Projects
None yet
Development

No branches or pull requests

2 participants