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

(🐞) Failure to narrow lambdas when using Protocol with __call__ #16797

Open
sg495 opened this issue Jan 19, 2024 · 1 comment
Open

(🐞) Failure to narrow lambdas when using Protocol with __call__ #16797

sg495 opened this issue Jan 19, 2024 · 1 comment
Labels
bug mypy got something wrong

Comments

@sg495
Copy link

sg495 commented Jan 19, 2024

Bug Report

Mypy fails to narrow lambdas and detect errors when using a Protocol with a __call__ method, in situations where using the equivalent Callable formulation results in narrowing and error detection.

To Reproduce

In the following snippet, type narrowing is performed on the lambda function passed to the validator argument of validate10, resulting in the implementation typo being caught by Mypy:

from typing import Callable, TypeVar

InstanceT = TypeVar("InstanceT")

def validate10(instance: InstanceT, validator: Callable[[InstanceT, int], bool]) -> bool:
    return validator(instance, 10)

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # Mypy error
# "MyClass" has no attribute "validatez"; maybe "validate"?

The following snippet is essentially equivalent to the above, but using Protocol in place of Callable. The type of the lambda function is no longer narrowed, and the implementation typo is not caught:

from typing Protocol, TypeVar

InstanceT = TypeVar("InstanceT")
InstanceT_contra = TypeVar("InstanceT_contra", contravariant=True)
class Validator(Protocol[InstanceT_contra]):

    def __call__(self, instance: InstanceT_contra, value: int, /) -> bool:
        ...

def validate10(instance: InstanceT, validator: Validator[InstanceT]) -> bool:
    return validator(instance, 10)

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # No error

I would expect the narrowing behaviour in the two snippets to be the same (or close enough).
Please note that the issue persists even when the type variable is removed:

class MyClass:
    def validate(self, value: int) -> bool:
        return value >= 10

class Validator(Protocol):

    def __call__(self, instance: MyClass, value: int, /) -> bool:
        ...

def validate10(instance: MyClass, validator: Validator) -> bool:
    return validator(instance, 10)

instance = MyClass()
validate10(instance, lambda self, value: self.validatez(value)) # No error

Pylance/Pyright (strict) detects the typo in all cases.

Your Environment

  • Mypy version used: 1.7.1
  • Mypy command-line flags: --strict
  • Python version used: 3.12
@sg495 sg495 added the bug mypy got something wrong label Jan 19, 2024
@sg495 sg495 changed the title (🐞) Failure to narrow lambdas when using Protocol with __call__. (🐞) Failure to narrow lambdas when using Protocol with __call__ Jan 19, 2024
@KotlinIsland
Copy link
Contributor

KotlinIsland commented Jan 23, 2024

I minified your example a bit: playground

from typing import Protocol, TypeVar

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

class P(Protocol[in_T]):
    def __call__(self, instance: in_T) -> object: ...

f: P[int] = lambda self: self.real  # Expression has type "Any"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants