Skip to content

Add static_assert to verify type constraints in mypy #5687

Closed
@TV4Fun

Description

@TV4Fun

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 TypeVars, 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 TypeVars or parameterized Generics.

I propose adding a static_assert statement, which would be ignored at runtime, but would be capable of evaluating statements about Types 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?

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions