Repository navigation
Mypy emits two conflicting results for reveal_type #10739
Description
Bug Report
My colleague @ahuhn and I encountered a situation where mypy emits two different outputs for one reveal_type call:
from decimal import Decimal
from typing import Union
def f(elem: Union[int, str, Decimal]) -> None:
if isinstance(elem, int):
return
for _ in range(3):
if isinstance(elem, (Decimal, int)) and elem > 10:
continue
reveal_type(elem)
return NoneRunning mypy emits two lines for the reveal_type call, one correct and the other incorrect. In the incorrect reveal statement, the type narrowing at the top of the function is lost:
$ mypy example.py
example.py:11: note: Revealed type is "Union[builtins.str, decimal.Decimal]" # expected
example.py:11: note: Revealed type is "Union[builtins.int, builtins.str, decimal.Decimal]" # wrong
Expected Behavior
Expected only one piece of output for one reveal_type, and for that output to maintain the type narrowing from the top of the function.
Your Environment
We encountered this on mypy 0.812, but it also repros on latest master (currently 416f57b) using python 3.8.6.
A variation of interest: if you add a line of code after the reveal_type, then the second reveal_type output broadens to include Any:
from decimal import Decimal
from typing import Union
def f(elem: Union[int, str, Decimal]) -> None:
if isinstance(elem, int):
return
for _ in range(3):
if isinstance(elem, (Decimal, int)) and elem > 10:
continue
reveal_type(elem)
elem += 1 # new compared to the example above
return Nonemypy:
$ mypy example.py
example.py:11: note: Revealed type is "Union[builtins.str, decimal.Decimal]" # correct
example.py:11: note: Revealed type is "Union[builtins.int, Any, builtins.str, decimal.Decimal]" # even wider, now including Any
example.py:12: error: Unsupported operand types for + ("str" and "int") # expected and correct
example.py:12: note: Left operand is of type "Union[str, Decimal]"
example.py:12: note: Left operand is of type "Union[int, Any, str, Decimal]"
Found 1 error in 1 file (checked 1 source file)
(In this example, it's trivial to get around the problem by removing the redundant isinstance(int) check, but in our actual code, it wasn't so straightforward.)
Activity
9999years commentedon Jun 30, 2021
I think I'm experiencing the same bug, and the revealed types have no overlap:
ahuhn commentedon Jul 12, 2021
I did some initial investigation into where the error is and determined that the initial isinstance check is filtering out
strandListas expected, and the second isinstance check is addingstrback in (butListis not added back in). I did some logging to confirm that it is related to taking a union of(str, int)and(int, Decimal), not that it's popping up to too high a frame and getting back to the initial typing starting point.x: Union[int, str, Decimal, List]x: Union[int, Decimal]x: Union[int, str, Decimal]I wrote a non-passing test for
mypy/test-data/unit/check-isinstance.test:When looking at the output, note that x is typed as
Union[int, str, Decimal], notUnion[int, str, Decimal, List]Fix conditional_type_map to not expand the type (python#10739)
Fix conditional_type_map to not expand the type (python#10739)
Fix conditional_type_map to not expand the type (python#10739)
ahuhn commentedon Aug 2, 2021
I made a PR to fix it! yay :)
I don't fully understand
meet_typesvsnarrow_declared_typeyet, so I'll take another look next time I have downtime to understand before I submit.ahuhn commentedon Aug 2, 2021
Also, maybe it should be a breaking change on some things and should be
meet_typesinstead ofnarrow_declared_type. Or something else entirely.I should think about other edge cases as well.