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

@overload interacts badly with @property even with the type: ignore workaround #9937

Open
jraygauthier opened this issue Jan 22, 2021 · 3 comments
Labels
bug mypy got something wrong topic-descriptors Properties, class vs. instance attributes topic-overloads

Comments

@jraygauthier
Copy link

Assuming a issue.py module:

$ mypy issue.py 
issue.py:86: error: Invalid self argument "MyContainerStr" to attribute function "bounds" with type "Callable[[MyContainerBase[int]], BoundsInt]"
issue.py:86: error: Argument 1 to "check_to_str_bounds" has incompatible type "BoundsInt"; expected "BoundsStr"
Found 2 errors in 1 file (checked 1 source file)

This occurs when I attempt to overload a property. It however does not occurs for the same overload on a plain method.

issue.py is:

from typing import (ClassVar, Generic, NamedTuple, Optional, Type, TypeVar,
                    Union, overload)

BoundsInt = NamedTuple("BoundsInt", [
    ("begin", int), ("end", int)])

BoundsStr = NamedTuple("BoundsStr", [
    ("begin", str), ("end", str)])

_T = TypeVar("_T", int, str)


class MyContainerBase(Generic[_T]):
    BOUND_TYPE = None  # type: ClassVar[Optional[Type[Union[BoundsInt, BoundsStr]]]]  # noqa

    def __init__(self, begin: _T, end: _T) -> None:
        assert self.BOUND_TYPE is not None
        if isinstance(begin, int) and isinstance(end, int):
            self._bounds = BoundsInt(begin, end)  # type: Union[BoundsInt, BoundsStr]  # noqa

        assert isinstance(begin, str) and isinstance(end, str)
        self._bounds = BoundsStr(begin, end)

    @overload  # type: ignore
    @property
    def bounds(
            self: 'MyContainerBase[int]'
    ) -> BoundsInt:
        ...

    @overload  # type: ignore
    @property
    def bounds(
            self: 'MyContainerBase[str]'
    ) -> BoundsStr:
        ...

    @property
    def bounds(self) -> Union[BoundsInt, BoundsStr]:
        """Return the recurrence period's bounds."""
        return self._bounds

    @overload
    def get_bounds(
            self: 'MyContainerBase[int]'
    ) -> BoundsInt:
        ...

    @overload
    def get_bounds(
            self: 'MyContainerBase[str]'
    ) -> BoundsStr:
        ...

    def get_bounds(self) -> Union[BoundsInt, BoundsStr]:
        """Return the recurrence period's bounds."""
        return self._bounds


class MyContainerInt(MyContainerBase[int]):
    BOUND_TYPE = BoundsInt


class MyContainerStr(MyContainerBase[str]):
    BOUND_TYPE = BoundsStr


def test() -> None:
    def check_to_int_bounds(bounds: BoundsInt) -> None:
        pass

    def check_to_str_bounds(bounds: BoundsStr) -> None:
        pass

    # Typechecks as expected:
    check_to_int_bounds(MyContainerInt(1, 1).bounds)
    # As expected, if uncommented, fails with:
    #   Argument 1 to "check_to_str_bounds" has incompatible type "BoundsInt";
    #   expected "BoundsStr"
    # check_to_str_bounds(MyContainerInt(1, 1).bounds)

    # Unexpectedly, if uncommented both of those fail with:
    #   Invalid self argument "MyContainerStr" to attribute
    #   function "bounds" with type "Callable[[MyContainerBase[int]],
    #   BoundsInt]"
    check_to_str_bounds(MyContainerStr("a", "b").bounds)
    # check_to_int_bounds(MyContainerStr(1.0, 1.0).bounds)

    # We do not have the above issue with the `get_bounds` method:

    # Typechecks as expected:
    check_to_int_bounds(MyContainerInt(1, 1).get_bounds())
    # As expected, if uncommented, fails.
    # check_to_str_bounds(MyContainerInt(1, 1).get_bounds())

    # Typechecks as expected:
    check_to_str_bounds(MyContainerStr("a", "b").get_bounds())
    # As expected, if uncommented, fails.
    # check_to_int_bounds(MyContainerStr(1.0, 1.0).get_bounds())

Your Environment

  • Mypy version used: 0.790
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files):
[mypy]
warn_unused_ignores = True
mypy_path = distropmc
disallow_any_generics = True
  • Python version used: 3.5.9
  • Operating system and version:
$ uname -a
Linux rgauthier-precision 5.4.79 #1-NixOS SMP Sun Nov 22 09:14:12 UTC 2020 x86_64 GNU/Linux
@jraygauthier jraygauthier added the bug mypy got something wrong label Jan 22, 2021
@jraygauthier jraygauthier changed the title @overload interect badly with @property even with the type: ignore workaround @overload interacts badly with @property even with the type: ignore workaround Jan 22, 2021
@AlexWaygood AlexWaygood added topic-overloads topic-descriptors Properties, class vs. instance attributes labels Apr 1, 2022
@ikonst
Copy link
Contributor

ikonst commented Mar 14, 2023

Slightly shorter repro:

from __future__ import annotations
from typing import overload, Generic, TypeVar

T = TypeVar('T')


class C(Generic[T]):
    @overload  # type:ignore[misc]
    @property
    def f(self: C[int]) -> int: raise NotImplementedError

    @overload  # type:ignore[misc]
    @property
    def f(self: C[str]) -> str: raise NotImplementedError

    @property  # type:ignore[misc]
    def f(self): raise NotImplementedError


c: C[str] = C()
_ = c.f  # E: Invalid self argument "C[str]" to attribute function "f" with type "Callable[[C[int]], int]"

Playground: https://mypy-play.net/?mypy=latest&python=3.11&gist=15958eb7a2388321bc5561aeebffefd7

@Dr-Irv
Copy link

Dr-Irv commented Jun 20, 2023

Here's a workaround as suggested here: microsoft/pyright#3071 (comment)

from __future__ import annotations
from typing import overload, Generic, TypeVar, Any

T = TypeVar("T")


class _fDescriptor:
    @overload
    def __get__(self, instance: C[int], owner: Any) -> int:
        ...

    @overload
    def __get__(self, instance: C[str], owner: Any) -> str:
        ...

    def __get__(self, instance: object, owner: Any) -> int | str:
        raise NotImplementedError


class C(Generic[T]):
    f: _fDescriptor


c: C[str] = C()
_ = c.f
reveal_type(c.f)
ci: C[int] = C()
reveal_type(ci.f)

This will reveal the types correctly.

@bswck
Copy link

bswck commented Nov 30, 2023

Bump! I've just encountered the same issue. I'm not really into creating a custom descriptor.

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-descriptors Properties, class vs. instance attributes topic-overloads
Projects
None yet
Development

No branches or pull requests

5 participants