Not sure this is a bug or not...
from typing import TypeVar
S = TypeVar('S')
def parenthesize(s: S) -> S:
if isinstance(s, str):
return '(' + s + ')'
elif isinstance(s, bytes):
return b'(' + s + b')'
raise TypeError("That's not a string, dude! It's a %s" % type(s))
This gets errors on both return lines:
demo.py: note: In function "parenthesize":
demo.py:7: error: Incompatible return value type: expected S`-1, got builtins.str
demo.py:9: error: Incompatible return value type: expected S`-1, got builtins.bytes
Why? The isinstance() checks look like they should provide sufficient guards.
I'm guessing that s might be of a non-str, non-bytes type, and mypy doesn't recognize "else: raise" as a type guard. I'm currently in day-job avoidance, so I'd better not go find out by replacing the "raise" with "return s".
The reason the example doesn't work is that after the isinstance() the type of s is str (or bytes), but nothing else changes -- in particular, the return type is still S. str is not a subtype of S, so mypy rejects the return statement.
To make the example work, mypy could perhaps somehow keep track of the fact that S must be a supertype of str after the first isinstance() check, and use this information when checking the return statement.
Oh dang. I totally missed that the errors were about the return type, not about the + operators. Oh well. Since I blogged about this and linked to this issue from the blog, you can expect some more drive-by comments.
Actually this particular program really is invalid because S might be a subtype of str; then isinstance(s, str) will be true but '(' + s + ')' is still only a str and not an S.
'(' + s + ')'
If you change both return statements to return s, though, the program is valid but mypy still rejects it, because it doesn't know how to use both pieces of information simultaneously: that s is of type str and also of type S.
@rwbarton True, I got it wrong. In any case, this is a pretty subtle issue and doesn't look like a high-priority one.