Description
Mypy's type checking works well in many cases, but there are some complex cases that are difficult to code for, and would be impossible or costly to check at runtime. I give an example of a case where this would be useful in #5666. While I give a slightly cumbersome possible solution there, another possibility is to use explicit casting:
from abc import ABC, abstractmethod
from typing import Generic, TypeVar, cast
_InputType = TypeVar('_InputType', contravariant=True)
_IntermediateType = TypeVar('_IntermediateType')
_OutputType = TypeVar('_OutputType', covariant=True)
class GenericBase(Generic[_InputType, _IntermediateType, _OutputType], ABC):
@abstractmethod
def first_step(self, pipeline_input: _InputType) -> _IntermediateType: ...
def second_step(self, state: _IntermediateType) -> _OutputType:
# By default, pass through state unmodified
return cast(_OutputType, state)
def execute(self, pipeline_input: _InputType) -> _OutputType:
state = self.first_step(pipeline_input)
return self.second_step(state)
This works, but it eliminates type safety, as I have to trust that the implementing class chooses _IntermediateType
and _OutputType
in a way that that cast makes sense. If these were not TypeVar
s, I could do a runtime check like assert issubclass(_IntermediateType, _OutputType)
to verify that this cast is safe, but obviously Python does not have enough information at runtime to do an issubclass
check with TypeVar
s or parameterized Generics
.
I propose adding a static_assert
statement, which would be ignored at runtime, but would be capable of evaluating statements about Type
s in static analysis. So for example, in the code above, I could add:
def second_step(self, state: _IntermediateType) -> _OutputType:
# By default, pass through state unmodified
static_assert issubclass(_IntermediateType, _OutputType)
return cast(_OutputType, state)
In the definition of the base class, this would be assumed to be true if there were any possible unification of _IntermediateType
and _OutputType
that would allow it to be true (defaulting to true or producing a warning if this is non-trivial to evaluate), but this static_assert
would be re-evaluated, whenever this class was subclassed/instantiated or this method was called, and if it could ever be determined to be false, would cause Mypy to raise an error.
Any thoughts on this?