I recently upgraded mypy from 1.17.0 to 1.18.2 The following code was successfully validated in the old mypy version (1.17.0), but fails in the new one (1.18.2):
_T = TypeVar('_T')
class Foo(Generic[_T], ABC):
pass
def to_foo(value: _T | Foo[_T],
checker: Callable[[Any], TypeIs[_T]],
factory: Callable[[_T], Foo[_T]]) -> Foo[_T]:
if checker(value):
return factory(value)
else:
return value
This is the error:
error: Incompatible return value type (got "_T | Foo[_T]", expected "Foo[_T]") [return-value]
mypy seems unhappy with the second return statement. It seems, it can not deduce, that if value is _T | Foo[_T] to begin with, and is not _T (the else case), that is has to be of type Foo[_T].
Although, I thought that is exactly what TypeIs does:
Type narrowing is applied in both the positive and negative case
When I reduce the problem further, I end up with the following function which still fails:
_T = TypeVar('_T')
def mypy_fails(x: _T | int, checker: Callable[[Any], TypeIs[_T]], default: int) -> int:
if checker(x):
return default
else:
return x
It seems to be related to the TypeVar, since the following function (with int and _T switched) still validates:
def mypy_validates(x: int | _T, checker: Callable[[Any], TypeIs[int]], default: _T) -> _T:
if checker(x):
return default
else:
return x
What is wrong with my initial code? Why does mypy_validates work? And why was it fine in the old mypy version, but not the newer one?
elsebranch ofif checker(T)should narrowxto the intersection of_T | intand~_T(not-_T). The problem is likely thatThas no upper bound, so we cannot compute this intersection reasonably, but at least removing the_Titself from union members would make sense.