mypy icon indicating copy to clipboard operation
mypy copied to clipboard

`@overload`s on `@abstractmethod`s shouldn't need an implementation

Open DetachHead opened this issue 2 years ago • 7 comments

from abc import ABC, abstractmethod
from typing import overload


class Foo(ABC):
    @overload # error: An overloaded function outside a stub file must have an implementation
    @abstractmethod
    def foo(self, value: int) -> int:
        ...

    @overload
    @abstractmethod
    def foo(self, value: str) -> str:
        ...

    # (this fake impl shouldnt be necessary, that's the impl class's responsibility)
    # @abstractmethod
    # def foo(self, value: str | int) -> str | int:
    #     ...


class Bar(Foo):
    @overload
    def foo(self, value: int) -> int:
        ...

    @overload
    def foo(self, value: str) -> str:
        ...

    def foo(self, value: str | int) -> str | int:
        return 1

https://mypy-play.net/?mypy=latest&python=3.10&gist=9c19741f8c5682b5854d59f3cf5f9ade

the implementation on the abstract class serves no purpose and should not be required imo

DetachHead avatar Nov 08 '21 07:11 DetachHead

Try this:

from abc import ABC, abstractmethod
from typing import overload


class Foo(ABC):
    @overload
    def foo(self, value: int) -> int:
        ...

    @overload
    def foo(self, value: str) -> str:
        ...

    @abstractmethod
    def foo(self, value: str | int) -> str | int:
        ...

class Bar(Foo):
    @overload
    def foo(self, value: int) -> int:
        ...

    @overload
    def foo(self, value: str) -> str:
        ...

    def foo(self, value: str | int) -> str | int:
        return 1

Works like a charm! 👍

sobolevn avatar Nov 11 '21 10:11 sobolevn

yeah, but the implementation is useless in the abstract method. i'd prefer if i coulkd just go

from abc import ABC, abstractmethod
from typing import overload


class Foo(ABC):
    @abstractmethod
    @overload
    def foo(self, value: int) -> int:
        ...

    @abstractmethod
    @overload
    def foo(self, value: str) -> str:
        ...

class Bar(Foo):
    @overload
    def foo(self, value: int) -> int:
        ...

    @overload
    def foo(self, value: str) -> str:
        ...

    def foo(self, value: str | int) -> str | int:
        return 1

DetachHead avatar Nov 12 '21 06:11 DetachHead

I agree the original declaration seems reasonable. FWIW, there seems to be a workaround using Protocols (low confidence in there not being something wrong with it):

from abc import ABC, abstractmethod
from typing import Protocol, overload


class FooMethod(Protocol):
    @overload
    def __call__(self, value: int) -> int:
        ...
    
    @overload
    def __call__(self, value: str) -> str:
        ...


class Foo(ABC):
    @property
    @abstractmethod
    def foo(self) -> FooMethod:
        ...


class Bar(Foo):
    @overload
    def foo(self, value: int) -> int:
        ...

    @overload
    def foo(self, value: str) -> str:
        ...

    def foo(self, value):
        return 1

alicederyn avatar Nov 28 '22 08:11 alicederyn

I'm not sure that this is the same issue, but at least very similar. It would be great to allow omitting implementation in if TYPE_CHECKING block as well:

from __future__ import annotations

from typing import overload, TYPE_CHECKING

if TYPE_CHECKING:
    @overload  # E: An overloaded function outside a stub file must have an implementation  [no-overload-impl]
    def f(self, obj: int) -> int: ...
    @overload
    def f(self, obj: str) -> str: ...
else:
    from other.module import f

Here's a playground with this sample. Implementation signature will be discarded anyway, and there is nothing to typecheck.

Discovered in this SO answer.

sterliakov avatar Jan 30 '23 19:01 sterliakov

@sterliakov i would raise a separate issue for that

DetachHead avatar Jan 30 '23 23:01 DetachHead

Some specific logic might be needed for this case. Though you couldn't instantiate Foo, it would normally be valid to do super().foo() in Bar. Mypy would need to show an error if foo is actually called elsewhere.

TeamSpen210 avatar Jan 31 '23 01:01 TeamSpen210

Would it be possible to make the base class a generic bases on T input type and U output type. Then in the implementation class add the overloads for each type of T and U?

Hmm nope this does not work:

import typing
import abc

InputTypes = typing.TypeVar('InputTypes')
OutputTypes = typing.TypeVar('OutputTypes')

class Schema(typing.Generic[InputTypes, OutputTypes]):
    @classmethod
    @abc.abstractmethod
    def validate(cls, arg: InputTypes) -> OutputTypes:
        pass

class IntOrStrSchema(Schema[typing.Union[int, str], typing.Union[int, str]]):
    @typing.overload
    @classmethod
    def validate(cls, arg: str) -> str: ...

    @typing.overload
    @classmethod
    def validate(cls, arg: int) -> int: ...

    @classmethod
    def validate(cls, arg: typing.Union[str, int]) -> typing.Union[str, int]:
        return arg

a = IntOrStrSchema.validate('a')
b = IntOrStrSchema.validate('1')

Errors:

validate_generic.py:15: error: Signature of "validate" incompatible with supertype "Schema"
validate_generic.py:15: note:      Superclass:
validate_generic.py:15: note:          @classmethod
validate_generic.py:15: note:          def validate(cls, arg: Union[int, str]) -> Union[int, str]
validate_generic.py:15: note:      Subclass:
validate_generic.py:15: note:          @overload
validate_generic.py:15: note:          @classmethod
validate_generic.py:15: note:          def validate(cls, arg: str) -> str
validate_generic.py:15: note:          @overload
validate_generic.py:15: note:          @classmethod
validate_generic.py:15: note:          def validate(cls, arg: int) -> int

spacether avatar Jul 11 '23 01:07 spacether