Skip to content

at runtime dataclass special-cases FunctionType, but at type-time mypy special-cases Callable, leading to mismatches #14869

Open
@glyph

Description

@glyph

Bug Report

If you declare an attribute on a dataclass to be Callable, Mypy assumes that its __get__ will not be called, even on a default attribute. But this is only true if the default is a FunctionType, not some other type of callable.

Similarly, the inverse is true: an abstract type (such as a callable Protocol) will tell mypy that __get__ will be called, even though if the concrete default value is a FunctionType, it won't be.

Specifically, the following program contains 2 type errors, but MyPy does not think so:

To Reproduce

from __future__ import annotations
from typing import Callable, Protocol, ParamSpec, TypeVar, Generic
from dataclasses import dataclass

def f() -> None:
    print("hello world")

def f2(self: object) -> None:
    print("bound self", self)

P = ParamSpec("P")
T = TypeVar("T")
R = TypeVar("R", covariant=True)

T_con = TypeVar("T_con", contravariant=True)

class BindableMethod(Protocol[T_con, R]):
    def __get__(self, instance: T_con, owner: None | type[object]) -> Callable[[], R]:
        ...

    def __call__(me, self: T_con) -> R:
        ...

class UnboundNonFunction(Generic[P, R]):
    def __call__(self) -> int:
        print("unbound")
        return 3
    def __get__(self, instance: object, owner: type | None) -> BoundNonFunction:
        print("binding", instance, owner)
        return BoundNonFunction(instance)

@dataclass(frozen=True)
class BoundNonFunction:
    instance: object
    def __call__(self) -> str:
        print("bound", self.instance)
        return "wat"

@dataclass
class FuncHolder():
    func: Callable[[], None] = f
    func2: BindableMethod[FuncHolder, None] = f2
    func3: Callable[[], int] = UnboundNonFunction()

FuncHolder().func()
try:
    FuncHolder().func2()
except Exception as e:
    print("whoops", str(e))
try:
    print(FuncHolder().func3() + 4)
except Exception as e:
    print("whoops 2", str(e))

https://mypy-play.net/?mypy=latest&python=3.11&gist=1ac4e7c090774489207dfbc737de2d61

Expected Behavior

I would expect runtime and type-time behavior to be consistent here. Specifically I just don't expect __get__ to be called on things with a Callable type.

Actual Behavior

Mypy succeeds, rather than reporting the type errors.

Your Environment

  • Mypy version used: mypy 1.1.1 (compiled: yes)
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.11.2 (python.org, macOS, aarch64)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions