So, the following example is obviously contrived but I tried to keep some verisimilitude to my actual situation. Now that I've whittled this down, I am sure I am missing something obvious. Consider a couple of types and a restricted Union:
from typing import Union, TypeVar, Optional, Generic, overload
class Foo:
def __init__(self, x: int)-> None:
self.x = x
def frobnicate(self) -> 'Foo':
return Foo((self.x + 42) // 42)
class Bar:
def __init__(self, y: int) -> None:
self.y = y
def frobnicate(self) -> 'Bar':
return Bar(self.y + 88)
MyType = TypeVar('MyType', Foo, Bar)
class Container(Generic[MyType]):
val: Optional[MyType]
def __init__(self, val: Optional[MyType]=None) -> None:
self.val = val
def transmogrify(arg: Optional[MyType]) -> Optional[MyType]:
if arg is None:
return None
else:
return arg.frobnicate()
def extract_stuff(x: Optional[int], cont: Container[MyType]) -> Optional[MyType]:
result: Optional[MyType]
if x is None:
result = None
elif x == 88 or x == 42:
result = transmogrify(cont.val)
else:
result = cont.val
return result
When I try to type-check this with mypy, I get the following errors:
mcve3.py:32: error: Value of type variable "MyType" of "transmogrify" cannot be "Optional[Foo]"
mcve3.py:32: error: Value of type variable "MyType" of "transmogrify" cannot be "Optional[Bar]"
I cannot make sense of this. I suspect it is a problem of the many nested unions? Note, in my actual code, I am using a custom singleton enum Null
, so wherever you see Optional[Something]
it's actually Union[Something, Null]
, but I don't think that makes a difference.
Now, if I remove the Optional
, i.e. Union
, it all plays nice:
from typing import Union, TypeVar, Optional, Generic, overload
class Foo:
def __init__(self, x: int)-> None:
self.x = x
def frobnicate(self) -> 'Foo':
return Foo((self.x + 42) // 42)
class Bar:
def __init__(self, y: int) -> None:
self.y = y
def frobnicate(self) -> 'Bar':
return Bar(self.y + 88)
MyType = TypeVar('MyType', Foo, Bar)
class Container(Generic[MyType]):
val: MyType
def __init__(self, val: MyType) -> None:
self.val = val
def transmogrify(arg: MyType) -> MyType:
if arg is None:
return None
else:
return arg.frobnicate()
def extract_stuff(x: int, cont: Container[MyType]) -> MyType:
if x is None:
return None
elif x == 88 or x == 42:
return transmogrify(cont.val)
else:
return cont.val
What am I missing about Union's here?
Note, I've tried abstracting out a base-class, and having Foo
and Bar
derive from an abstract base class class MyType(metaclass=abc.Meta)
, but a very similar error pops up.
Edit to Add:
(py37) Juans-MBP: juan$ mypy --version
mypy 0.620
This seems to be a bug that was fixed somewhat recently in mypy. I was able to repro the problem in your first snippet by using mypy 0.630 but was unable to repro using both mypy 0.641 and the latest version of mypy on master.
I very loosely suspect the bug was fixed by https://github.com/python/mypy/pull/5699, but don't know for certain (and don't feel like checking, tbh).
You can monitor mypy's blog if you'd like to be notified of future releases to avoid similar situations in the future. New releases are made roughly every 6 weeks to two months or so. -- the next release is slated to come out in roughly two weeks or so from time of writing.