Search code examples
pythonpython-typingmypy

mypy reporting error in generic function on return statement


With the code below

def incr[T: int | None](value: T) -> T:
if value is None:
    return value
return value + 1

incr(None)
incr(1)

Running mypy gives error: main.py:4: error: Incompatible return value type (got "int", expected "T") [return-value] Found 1 error in 1 file (checked 1 source file)

Why is this happening? It appears to me that the line is returning correct type. Note: the goal of such type annotation was to show that the function returns None only when given None as input


Solution

  • T: int | None is a type variable with an upper bound. It indicates that if you pass any subtype of int | None, you will get that subtype back.

    mypy is warning you that if you pass a subclass of int, the function behaves wrongly at runtime. This is due to the definition of builtins.int__add__, which returns int (rather than typing.Self):

    import typing as t
    
    def incr[T: int | None](value: T) -> T:
        if value is None:
            return value
        return value + 1  # error: Incompatible return value type (got "int", expected "T")  [return-value]
    
    class MyInt(int):
        pass
    
    >>> result: t.Final = incr(MyInt(3))
    >>> if t.TYPE_CHECKING:
    ...    reveal_type(result)  # note: Revealed type is "MyInt"
    >>> print(type(result))
    <class 'int'>
    

    The solution is to use constrained type variables, which behaves by up-casting the result to the appropriate constrained type:

    def incr[T: (int, None)](value: T) -> T: ...
    
    >>> if t.TYPE_CHECKING:
    ...    reveal_type(result)  # note: Revealed type is "builtins.int"