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

Expression has type "function", variable has type "Callable[[], None]" #10740

Closed
FFY00 opened this issue Jun 29, 2021 · 8 comments
Closed

Expression has type "function", variable has type "Callable[[], None]" #10740

FFY00 opened this issue Jun 29, 2021 · 8 comments
Labels
bug mypy got something wrong good-second-issue topic-join-v-union Using join vs. using unions topic-ternary-expression a if b else c

Comments

@FFY00
Copy link
Member

FFY00 commented Jun 29, 2021

To Reproduce

from typing import Callable

class Test:
    def method1(self) -> None:
        pass

    def method2(self, optional: int = 0) -> None:
        pass

    def test(self, something: bool) -> None:
        action: Callable[[], None]
        action = self.method1 if something else self.method2

Expected Behavior

I believe the code above should validate in mypy.

Actual Behavior

$ mypy example.py
example.py:12: error: Incompatible types in assignment (expression has type "function", variable has type "Callable[[], None]")
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.9.5
  • Operating system and version: Arch Linux
@FFY00 FFY00 added the bug mypy got something wrong label Jun 29, 2021
@isaacwritescode7
Copy link

isaacwritescode7 commented Jul 12, 2021

This seems to be expected behavior, if the boolean something is false you reassign action to method2 which has an argument however in your code this creates a conflict because when you first typed action you said that it will never have any arguments but now it does, creating incompatible types in the assignment. To make mypy happy I presume that you will have to use a union. (Disclaimer: I am new to mypy so this may very well be incorrect)

from typing import Callable
from typing import Union


class Test:
    def method1(self) -> None:
        pass

    def method2(self, optional: int = 0) -> None:
        pass

    def test(self, something: bool) -> None:
        action: Union[Callable[[], None], Callable[[int], None]]
        action = self.method1 if something else self.method2

@erictraut
Copy link

erictraut commented Jul 12, 2021

This looks like a mypy bug to me. It type checks fine with pyright.

To work around the bug, you can do the following:

    def test(self, something: bool) -> None:
        action1: Callable[[], None] = self.method1
        action2: Callable[[], None] = self.method2
        action: Callable[[], None] = action1 if something else action2

@hauntsaninja
Copy link
Collaborator

Yeah, I think there are two improvements here. One, mypy uses joins for ternary expressions (#9264 and #5392 are related) where maybe a union / meet would be better. Two, the join logic doesn't take into account optional parameters, so you get builtins.function where maybe something sophisticated could come up with Callable[[], None].

pollenjp added a commit to pollenjp/pycord that referenced this issue Jan 24, 2022
```
discord/backoff.py:73: error: Incompatible types in assignment (expression has type "function", variable has type "Callable[..., float]")
```

- Refer to <python/mypy#10740 (comment)>
@JelleZijlstra JelleZijlstra added the topic-join-v-union Using join vs. using unions label Mar 19, 2022
@mdczaplicki
Copy link

Any update here?

@KotlinIsland
Copy link
Contributor

KotlinIsland commented Mar 14, 2024

The inverse of this situation (#17017) is when the callables are actually incompatible and are joined into function instead of object:

from typing import Callable

class C:
    def __init__(self, i: int=1): ...

c1: Callable[[], object] = C
c2: Callable[[int], object] = C
reveal_type(c1 if bool() else c2)  # Revealed type is "builtins.function"

raylu added a commit to raylu/pigwig that referenced this issue Apr 8, 2024
raylu added a commit to raylu/pigwig that referenced this issue Apr 8, 2024
raylu added a commit to raylu/pigwig that referenced this issue Apr 8, 2024
raylu added a commit to raylu/pigwig that referenced this issue Apr 8, 2024
@ilevkivskyi
Copy link
Member

After switching to inferring unions for ternary, this now works on master.

@KotlinIsland
Copy link
Contributor

@ilevkivskyi why was this issue closed? none of the problems listed were resolved, simply masked by a behaviour change.
this issue is not regarding conditional expressions, it is regarding a specific defect in the joining behaviour of Callables, if the handling of this exact scenario was replaced with the work properly behaviour (unionization), that doesn't mean that this defect has been fixed

playground:

from typing import *

T = TypeVar("T")

def join(t1: T, t2: T) -> T:
    return t1
    
def f1() -> None: ...

def f2(optional: int = 0) -> None: ...


action: Callable[[], None]
action = join(f1, f2)  # Incompatible types in assignment (expression has type "function", variable has type "Callable[[], None]")  [assignment]
        
class C:
    def __init__(self, i: int=1): ...

c1: Callable[[], object] = C
c2: Callable[[int], object] = C
reveal_type(join(c1, c2))  # Revealed type is "builtins.function"

@KotlinIsland
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong good-second-issue topic-join-v-union Using join vs. using unions topic-ternary-expression a if b else c
Projects
None yet
Development

No branches or pull requests

9 participants