Skip to content

Decorated __init__ confuses callback type if decorator is defined with type alias or if decorator is imported using relative import #11293

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

Open
posita opened this issue Oct 8, 2021 · 3 comments
Labels
bug mypy got something wrong topic-type-alias TypeAlias and other type alias issues

Comments

@posita
Copy link
Contributor

posita commented Oct 8, 2021

This one's just weird. I'm not sure if this is a dup of #1927, but I figured I'd raise it anyway.

# test_case.py
from typing import Callable, Iterable, TypeVar, Union

_T = TypeVar("_T")

def _identity(__: _T) -> _T:
    return __

# decr: Callable[[_T], _T] = _identity  # <-- this approach works
DecoratorT = Callable[[_T], _T]
decr: DecoratorT = _identity  # <-- this causes the false positives below

class Result:
    @decr
    def __init__(self):
        pass

res = Result()  # ; reveal_type(res)  # -> Any (should be test_case.Result)

def some_results(op: Callable[[], Union[Result, Iterable[Result]]]) -> Iterable[Result]:
    result_or_results = op()
    results: Iterable[Result]
    if isinstance(result_or_results, Result):
        results = (result_or_results,)  # ; reveal_type(result_or_results)  # -> (should be test_case.Result)
    else:
        results = result_or_results  # ; reveal_type(result_or_results)  # -> Union[test_case.Result, typing.Iterable[test_case.Result]] (should be typing.Iterable[test_case.Result])
    return results
% python --version ; mypy --version
Python 3.9.7
mypy 0.910
% mypy --config-file=pyproject.toml test_case.py
test_case.py:26: error: Incompatible types in assignment (expression has type "Union[Result, Iterable[Result]]", variable has type "Iterable[Result]")
Found 1 error in 1 file (checked 1 source file)

Note that if DecoratorT is defined as follows, things work again:

# …
class DecoratorT(Protocol):
    def __call__(self, __: _T) -> _T:
        ...
decr: DecoratorT = _identity  # <-- all good
# …

But if we move the decorator into its own module and use a relative import, it breaks no matter how we define the decorator type:

# test_case.py
from typing import Callable, Iterable, TypeVar, Union
# from test_case_import import decr  # <-- this works
from .test_case_import import decr  # <-- this causes the false positives below 

class Result:
    @decr
    def __init__(self):
        pass

res = Result()  # ; reveal_type(res)  # -> Any (should be test_case.Result)

def some_results(op: Callable[[], Union[Result, Iterable[Result]]]) -> Iterable[Result]:
    result_or_results = op()
    results: Iterable[Result]
    if isinstance(result_or_results, Result):
        results = (result_or_results,)  # ; reveal_type(result_or_results)  # -> (should be test_case.Result)
    else:
        results = result_or_results  # ; reveal_type(result_or_results)  # -> Union[test_case.Result, typing.Iterable[test_case.Result]] (should be typing.Iterable[test_case.Result])
    return results
# test_case_import.py
from typing import Callable, TypeVar
_T = TypeVar("_T")

def _identity(__: _T) -> _T:
    return __

decr: Callable[[_T], _T] = _identity  # <-- doesn't matter how this is defined
% mypy --config-file=pyproject.toml test_case.py test_case_import.py
test_case.py:19: error: Incompatible types in assignment (expression has type "Union[Result, Iterable[Result]]", variable has type "Iterable[Result]")
Found 1 error in 1 file (checked 2 source files)

Using the Protocol approach does not salvage the second (relative import) scenario.

@posita posita added the bug mypy got something wrong label Oct 8, 2021
@posita
Copy link
Contributor Author

posita commented Oct 8, 2021

What does note: Revealed type is "def [_T] (_T`-1) -> _T`-1" mean? (I found that when looking into the real world case that inspired the above distillations.)

@JelleZijlstra
Copy link
Member

It's a function with the type signature (T) -> T where T is a typevar, i.e. an identity function.

@posita
Copy link
Contributor Author

posita commented Oct 9, 2021

It's a function with the type signature (T) -> T where T is a typevar, i.e. an identity function.

Good to know it's legit. I've never seen that before. Is it documented anywhere? I was unable to find it in the official docs. (Apologies if I missed it.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-alias TypeAlias and other type alias issues
Projects
None yet
Development

No branches or pull requests

3 participants