Skip to content

function incompatible with Callable[..., Any] #21804

Description

@TTsangSC

Bug Report

In my understanding Callable[..., Any] is about as general as callables get, and therefore arbitrary functions should be compatible therewith. However, while it does accept typed functions (def (...) -> ...) and types.FunctionTypes, if the typing info is at any point lost and the function resolves to a bare function, Callable[..., Any] would no longer accept it.

(A clear and concise description of what the bug is.)

To Reproduce

(Gist: https://gist.github.com/mypy-play/da65c6f9c2dfc5bcc565ca5df5f1c9f1)

Consider the following example:

from collections.abc import Callable
from types import FunctionType
from typing import Any, cast, reveal_type


def foo0() -> None:
    ...


def foo1(x: int) -> None:
    ...


def foo2(x: str, y: float) -> None:
    ...

def select_from_dict(arg: int) -> None:
    foo: Callable[..., Any]
    foobar = reveal_type({
        1: foo0,
        2: foo1,
        3: foo2,
    })[arg]
    reveal_type(foobar)
    foo = foobar


def assign_from_chained_ternary(arg: int) -> None:
    foo: Callable[..., Any] = reveal_type(
        foo0
        if arg == 1 else
        foo1
        if arg == 2 else
        foo2
    )


def direct_assignment(arg: int) -> None:
    foo: Callable[..., Any]
    if arg == 1:
        foo = reveal_type(foo0)
    elif arg == 2:
        foo = reveal_type(foo1)
    elif arg == 3:
        foo = reveal_type(foo2)
    else:
        raise RuntimeError


def assign_with_explicit_cast(arg: int) -> None:
    foo: Callable[..., Any]
    if arg == 1:
        foo = reveal_type(cast(FunctionType, foo0))
    elif arg == 2:
        foo = reveal_type(cast(FunctionType, foo1))
    elif arg == 3:
        foo = reveal_type(cast(FunctionType, foo2))
    else:
        raise RuntimeError

while select_from_dict(), assign_from_chained_ternary(), direct_assignment(), and assign_with_explicit_cast() should be analogous in behaviors, we see from the mypy output that:

  • def (...) -> ..., (def (...) -> ...) | (def (...) -> ...) | ... and types.FuctionType are all considered compatible with Callable[..., Any], as they should.
  • Meanwhile, by shoving the functions into a dict they are resolved into opaque functions, which somehow is considered incompatible.

Expected Behavior

The code should type-check.

Actual Behavior

main.py:19: note: Revealed type is "dict[int, function]"
main.py:24: note: Revealed type is "function"
main.py:25: error: Incompatible types in assignment (expression has type "function", variable has type "Callable[..., Any]")  [assignment]
main.py:30: note: Revealed type is "(def ()) | (def (x: int)) | (def (x: str, y: float))"
main.py:41: note: Revealed type is "def ()"
main.py:43: note: Revealed type is "def (x: int)"
main.py:45: note: Revealed type is "def (x: str, y: float)"
main.py:53: note: Revealed type is "types.FunctionType"
main.py:55: note: Revealed type is "types.FunctionType"
main.py:57: note: Revealed type is "types.FunctionType"
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 2.3.0 (Playground)
  • Mypy command-line flags: nil
  • Mypy configuration options from mypy.ini (and other config files): nil
  • Python version used: 3.14

Possible related issues

#10740

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