Description
- Are you reporting a bug, or opening a feature request?
A potential bug
So, I've posted a corresponding question on StackOverflow, but it hasn't gotten much traction.
Here is my basic situation: I'm trying to use a Protocol that would include a couple of classmethods.
The protocol is generic too, and the signatures involves self-types:
from dataclasses import dataclass
from typing import Union, ClassVar, TypeVar, Generic, Type
from typing_extensions import Protocol
_P = TypeVar('_P', bound='PType')
class PType(Protocol):
@classmethod
def maximum_type_value(cls: Type[_P]) -> _P:
...
@classmethod
def minimum_type_value(cls: Type[_P]) -> _P:
...
def predecessor(self: _P) -> _P:
...
def successor(self: _P) -> _P:
...
@dataclass
class MyInteger:
value: int
_MAX: ClassVar[int] = 42
_MIN: ClassVar[int] = -42
def __post_init__(self) -> None:
if not (self._MIN <= self.value <= self._MAX):
msg = f"Integers must be in range [{self._MIN}, {self._MAX}]"
raise ValueError(msg)
@classmethod
def maximum_type_value(cls) -> MyInteger:
return MyInteger(cls._MAX)
@classmethod
def minimum_type_value(cls) -> MyInteger:
return MyInteger(cls._MIN)
def predecessor(self) -> MyInteger:
return MyInteger(self.value - 1)
def successor(self) -> MyInteger:
return MyInteger(self.value + 1)
@dataclass
class Interval(Generic[_P]):
low: _P
high: _P
interval = Interval(MyInteger(1), MyInteger(2))
def foo(x: PType) -> PType:
return x
foo(MyInteger(42))
When I try to type-check this, I get the following errors:
(py37) Juans-MacBook-Pro: juan$ mypy mcve.py
mcve.py:46: error: Value of type variable "_P" of "Interval" cannot be "MyInteger"
mcve.py:49: error: Argument 1 to "foo" has incompatible type "MyInteger"; expected "PType"
mcve.py:49: note: Following member(s) of "MyInteger" have conflicts:
mcve.py:49: note: Expected:
mcve.py:49: note: def maximum_type_value(cls) -> <nothing>
mcve.py:49: note: Got:
mcve.py:49: note: def maximum_type_value(cls) -> MyInteger
mcve.py:49: note: Expected:
mcve.py:49: note: def minimum_type_value(cls) -> <nothing>
mcve.py:49: note: Got:
mcve.py:49: note: def minimum_type_value(cls) -> MyInteger
Why are the return types registering as <nothing>
? When omit the annotation for the cls
argument in the protocol, I get the following error:
mcve.py:46: error: Value of type variable "_P" of "Interval" cannot be "MyInteger"
mcve.py:49: error: Argument 1 to "foo" has incompatible type "MyInteger"; expected "PType"
mcve.py:49: note: Following member(s) of "MyInteger" have conflicts:
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] maximum_type_value(cls) -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def maximum_type_value(cls) -> MyInteger
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] minimum_type_value(cls) -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def minimum_type_value(cls) -> MyInteger
Which, quite frankly, makes even less sense to me.
If I make the classmethods instance methods, I receive no error. I would want to avoid this, since it would require a more hefty re-design.
If I try to use a staticmethod (which would be an easier workaround) it also fails:
_P = TypeVar('_P', bound='PType')
class PType(Protocol):
@staticmethod
def maximum_type_value() -> _P:
...
@staticmethod
def minimum_type_value() -> _P:
...
def predecessor(self: _P) -> _P:
...
def successor(self: _P) -> _P:
...
@dataclass
class MyInteger:
value: int
_MAX: ClassVar[int] = 42
_MIN: ClassVar[int] = -42
def __post_init__(self) -> None:
if not (self._MIN <= self.value <= self._MAX):
msg = f"Integers must be in range [{self._MIN}, {self._MAX}]"
raise ValueError(msg)
@staticmethod
def maximum_type_value() -> MyInteger:
return MyInteger(MyInteger._MAX)
@staticmethod
def minimum_type_value() -> MyInteger:
return MyInteger(MyInteger._MIN)
def predecessor(self) -> MyInteger:
return MyInteger(self.value - 1)
def successor(self) -> MyInteger:
return MyInteger(self.value + 1)
error:
mcve.py:46: error: Value of type variable "_P" of "Interval" cannot be "MyInteger"
mcve.py:49: error: Argument 1 to "foo" has incompatible type "MyInteger"; expected "PType"
mcve.py:49: note: Following member(s) of "MyInteger" have conflicts:
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] maximum_type_value() -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def maximum_type_value() -> MyInteger
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] minimum_type_value() -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def minimum_type_value() -> MyInteger
What is wrong with my Protocol / class definition? Am I missing something obvious?
I am using mypy version 0.620