python / python/mypy

Dataclass doesn't match a protocol if a field is Callable (expected settable variable, got read-only attribute)

Open
#18,179 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Bug Report

A dataclass containing a Callable field doesn't match the protocol. On the other hand if the field is defined using a callback protocol (another protocol with __call__) or the class is not a dataclass, no error is reported.

test.py:26: error: Incompatible types in assignment (expression has type "FooDC", variable has type "FooProto")  [assignment]
test.py:26: note: Protocol member FooProto.f expected settable variable, got read-only attribute

The mypy's subtypes.py:get_member_flags() function contains:

if v.is_property:
    return {IS_VAR}

which doesn't take the v.is_settable_property into account while it is set for Callables in plugins/dataclasses.py:_propertize_callables().

To Reproduce

https://mypy-play.net/?mypy=latest&python=3.12&gist=5c77ed1a30b03cba5cf0ba8e710a4214

import typing
import collections.abc
import dataclasses



CAC = collections.abc.Callable[..., None]

class FooProto(typing.Protocol):
    f: CAC

class Foo:
    f: CAC

    def __init__(self, f: CAC):
        self.f = f

@dataclasses.dataclass(frozen=False)
class FooDC:
    f: CAC

foo: FooProto = Foo(lambda: None)
# only foo_dc fails:
# test.py:23: error: Incompatible types in assignment (expression has type "FooDC", variable has type "FooProto")  [assignment]
# test.py:23: note: Protocol member FooProto.f expected settable variable, got read-only attribute
foo_dc: FooProto = FooDC(lambda: None)


class CallbackProto(typing.Protocol):
    def __call__(self, *args, **kwargs) -> None: ...

class FooProtoCallback(typing.Protocol):
    f: CallbackProto

class FooCallback:
    f: CallbackProto

    def __init__(self, f: CAC):
        self.f = f

@dataclasses.dataclass(frozen=False)
class FooDCCallback:
    f: CallbackProto

foo_callback: FooProtoCallback = FooCallback(lambda: None)
foo_dc_callback: FooProtoCallback = FooDCCallback(lambda: None)

Expected Behavior

I expect a dataclass with a Callable to match the protocol.

Actual Behavior

The dataclass doesn't match the protocol.

Your Environment

  • Mypy version used: 1.13.0
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.11.2

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 subtypes.py at get_member_flags() and compare its handling of v.is_property with the v.is_settable_property state set by plugins/dataclasses.py:_propertize_callables(). Use the issue's reproduction with mypy 1.13.0 to verify the behavior, then confirm that the dataclass with a Callable field matches the protocol without the reported assignment error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.