Description
Bug Report
This is a pretty ridiculous bug, I'm surprised it has not shown up until now. 😆 It goes back all the way to mypy 0.800. I will open a PR with a fix shortly.
Thankfully this isn't a problem in practice, since None comparisons are usually done with is None
instead of == None
.
def func(val: str | None) -> None:
if val == None:
reveal_type(val) # ❌ Revealed type is "builtins.str"
if val in (None,):
reveal_type(val) # ❌ Revealed type is "builtins.str"
This is because type narrowing logic internally uses is_optional()
to check whether a value can be None. is_optional()
only considers UnionType
, whereas NoneType
value may also have value None.
To Reproduce
Example from above: https://mypy-play.net/?mypy=1.4.1&python=3.11&gist=fc584a3bb43ece3480b70b0b6b7a1499
Expected Behavior
== None
condition should be narrowed to remove Optional
wrapping.
Actual Behavior
After applying my fix:
def func(val: str | None) -> None:
if val == None:
reveal_type(val) # ✅ Revealed type is "Union[builtins.str, None]"
if val in (None,):
reveal_type(val) # ✅ Revealed type is "Union[builtins.str, None]"
The narrowing logic here could be further improved, but that's out of scope for the bugfix pull request
Your Environment
- Mypy version used: git master, 1.4.1, 0.800
- Mypy command-line flags: -
- Mypy configuration options from
mypy.ini
(and other config files): - - Python version used: 3.11