-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bool
is not treated the same as Union[Literal[True], Literal[False]]
in overloads
#10194
Comments
It seems that the final type declaration of @overload
def foo(inplace: Literal[True]) -> None: ...
@overload
def foo(inplace: Literal[False]) -> int: ...
# Added overload
@overload
def foo(inplace: bool) -> Optional[int]: ...
# Actual definition
def foo(inplace: bool) -> Optional[int]: ...
inplace_foo: bool
reveal_type(foo(inplace_foo)) # Revealed type is 'Union[builtins.int, None]' This solution could be used for the time being. Still, I think that the non-overload signature typings should be included by default, if present. |
Thanks @DevilXD - yes, for the time being I'm adding in an extra overload of the |
It looks like this is intended to work like that, based on the Python documentation (and PEP 484):
Adding an overload like this is just the way to go here, even if it feels like boilerplate code. |
@DevilXD In theory, MyPy could use the overloads to figure out that all of possible Boolean values are covered. It may not be worth the trouble though compared with adding the overload you showed. |
This adds a lot of complexity. Right now, it's quite simple - look at all the overloads, and figure out the one matching, and use that. Once you get into "process all overloads to see if every possibility is covered", it gets unnecessarily complex. Again, adding an overload like this, is the way to go, even if it feels like boilerplate code ¯\_(ツ)_/¯ |
@DevilXD Makes sense, I agree 😄 |
closing as it seems that $ cat t.py
from typing import Literal, Union
def foo(x: Literal[True, False]) -> None:
return None
x: bool
foo(x)
$ mypy t.py
t.py:7: error: Argument 1 to "foo" has incompatible type "bool"; expected "Union[Literal[True], Literal[False]]"
Found 1 error in 1 file (checked 1 source file) |
I think it is an overload-specific thing - actually a bug even. EDIT: It's slightly the same: |
Not sure what you mean here - it does take unions: $ cat t.py
from typing import Literal, Union, overload, Sequence
@overload
def foo(x: str) -> Sequence[int]: ...
@overload
def foo(x: int) -> int: ...
def foo(x): ...
x: Union[int, str]
reveal_type(foo(x))
$ mypy t.py
t.py:12: note: Revealed type is 'Union[builtins.int, typing.Sequence[builtins.int]]' |
Well... I have no idea how MyPy does that then, as it technically shouldn't do that. On the linked issue, you can see someone explaining that it'd lead to quadratic complexity - not sure how true it is now. Only one overload should be matched, picked, and used, for each invocation. It seems like MyPy is doing some additional work under the hood, to provide this union functionality - which is even more reason you should keep this issue open, as it's clearly not working as intended (with the bool example you've provided). |
It's behaving as documented here https://mypy.readthedocs.io/en/stable/more_types.html#type-checking-calls-to-overloads:
The reason this doesn't happen in the example I originally posted is that
I think they were referring to when the parameter you're overloading has parameters which comes before it which take defaults, see here for some examples of where you have to provide lots of overloads. Even then though, I think the complexity would be 2^n rather than n^2 (for each default parameter preceding the one you're overloading, you need to take care of the case where it's present and the one where it's absent) |
FYI as of ef43416 this program now type checks: from typing import Literal, Union
def foo(x: Literal[True, False]) -> None:
return None
x: bool
foo(x) IMO this issue should be reopened. |
What if I'm no expert, but seems like this should fail. As @MarcoGorelli says, Edit: bool is not subclassable, so my argument doesn't apply. |
ATM To be more specific, in mypy's eyes |
@A5rocks Yes, I agree with you that that I think that may be incorrect and the issue should be reopened. You may be interested to know that pyright also passes on your test, and it might be worthwhile asking the expert @erictraut what he thinks? |
From a type perspective, I think it's therefore fine for the code sample posted above by @A5rocks to pass type checking, as it currently does with mypy and pyright. from typing import Literal
def foo(x: Literal[True, False]) -> None:
return None
def func(x: bool):
foo(x) # No Error (for mypy or pyright) Although def func(val: bool):
match val:
case True:
print("True!")
case False:
print("False!")
case _:
assert_never(val) # No error (for mypy or pyright) In this case, pyright and mypy expand I'm not convinced that from typing import Literal, overload
@overload
def func(x: Literal[True]) -> int: ...
@overload
def func(x: Literal[False]) -> str: ...
def func(x: bool) -> int | str:
return 0 if x else ""
def foo(x: bool):
func(x) # Error: no overload matches (both mypy and pyright) So, from my perspective, mypy is doing the right thing currently, and I would leave this bug closed. Mypy and pyright behaviors are currently in alignment in this regard. |
I think that's perfectly fine, but one of the key ingredients that needs to be mentioned is that both from enum import Enum
class Color(Enum):
red = 1
green = 2
class T(Color): # Raises TypeError.
pass
class U(bool): # Raises TypeError.
pass If these types were subclassable, then the assumption that these types are a known exhaustive set of literals would not hold. |
Bug Report
The Type checking calls to overload docs read
To Reproduce
https://mypy-play.net/?mypy=latest&python=3.9&gist=051875ec08b32acb049577f7cd6085f7
Expected Behavior
I would've expected that typing a variable as
bool
would make it behave the same way asUnion[Literal[True], Literal[False]]
Actual Behavior
bool
is not treated the same asUnion[Literal[True], Literal[False]]
. However, that's all thatbool
can be, right, eitherTrue
orFalse
?Your Environment
mypy.ini
(and other config files):See the link to the
mypy
playground above to reproduceThe text was updated successfully, but these errors were encountered: