Skip to content

Decorated function makes type-check fail if done inside of method from the class #9266

Closed
@fabioz

Description

@fabioz

I believe this is a bug (explanation below).

I expected to be able to statically check a case by checking whether self matches a given protocol (with the check_implements below).

This seems to work, but if the method is decorated (with the implements as shown in the code below) it doesn't validate properly that a method return type is wrong.

i.e.: in the code below I expected that the _: IFoo = check_implements(self) line gives an error because the protocol declaration expects a bool return, whereas the method implementation says it returns a str.

I think this is a bug because the same type-check later on says that the implementation doesn't conform to the protocol if done outside of the class -- as _: IFoo = check_implements(Foo()), so, it does something on __init__ and a different thing if out of the __init__. Besides, it works if the function is not decorated.

Note: even if I do _: IFoo = check_implements(Foo()) in the __init__ it doesn't validate properly (so, it doesn't seem related to self -- and it works as expected if the function is not decorated).

from typing import TypeVar, Protocol, Any, Callable
import functools

F = TypeVar('F', bound=Callable[..., Any])

def implements(method: Any) -> Callable[[F], F]:
    """
    Meant to be used as

    class B:
        @implements(IExpectedProtocol.method)
        def method(self):
            pass

    Useful for knowing whether a name still exists in the interface and document
    what's being implemented.

    In runtime validates that the name passed is the same name implemented.
    """
    @functools.wraps(method)
    def wrapper(func):
        if func.__name__ != method.__name__:
            msg = "Wrong @implements: %r expected, but implementing %r."
            msg = msg % (func.__name__, method.__name__)
            raise AssertionError(msg)

        return func

    return wrapper

T = TypeVar("T")

def check_implements(x: T) -> T:
    """
    This is to be used in the constructor of a class which is expected to
    implement some protocol. It can be used as:

    def __init__(self):
        _: IExpectedProtocol = check_implements(self)

    Mypy should complain if `self` is not implementing the IExpectedProtocol.
    """
    return x

class IFoo(Protocol):

    def some_method(self) -> bool:
        pass

class Foo(object):

    def __init__(self) -> None:  # give type
        _: IFoo = check_implements(self)  # Issue: Mypy does not report error here

    @implements(IFoo.some_method)  # Note: if this implements is removed it reports the error at the __init__!
    def some_method(self) -> str:  # Note: return value does not match protocol!
        pass

_: IFoo = check_implements(Foo())  # Mypy properly reports error here (with or without the implements decorator).

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