Repository navigation
False narrowing with repeated assignment inside if resulting in bad false negative #18492
Copy link
Copy link
Closed
Closed
Copy link
Labels
bugmypy got something wrongmypy got something wronggood-second-issuetopic-type-narrowingConditional type narrowing / binderConditional type narrowing / binder
Description
Bug Report
In a function, mypy fails to detect an incompatible assignment in a function, if that is after a __get_item__ access inside a branch. Weirdly, mypy correctly deduces the types, as shown with reveal_type but does not complain about the incorrect assignment anyways.
To Reproduce
I would have liked to give a playground link, but the MWE involves a pandas.DataFrame which seems not to work on the playground.
import pandas as pd
# fails as expected
some_condition: bool = "123".isnumeric()
list_of_dicts = [{"some": 1, "columns": 2, "will": 3, "go": 4, "here": 5}]
if some_condition > 0:
output_df = pd.DataFrame(list_of_dicts)
output_df = output_df[["some", "columns", "here"]]
else:
output_df = None # fails here
def fails_as_expected_1(some_condition: bool) -> pd.DataFrame:
list_of_dicts = [{"some": 1, "columns": 2, "will": 3, "go": 4, "here": 5}]
output_df = pd.DataFrame(list_of_dicts)
output_df = output_df[["some", "columns", "here"]]
output_df = None # fails here
other_df: pd.DataFrame = output_df
return other_df
def fails_as_expected_2(some_condition: bool) -> pd.DataFrame:
list_of_dicts = [{"some": 1, "columns": 2, "will": 3, "go": 4, "here": 5}]
if some_condition > 0:
output_df = pd.DataFrame(list_of_dicts)
else:
output_df = None
other_df: pd.DataFrame = output_df # fails here
return other_df
def does_not_fail_but_should(some_condition: bool) -> pd.DataFrame:
list_of_dicts = [{"some": 1, "columns": 2, "will": 3, "go": 4, "here": 5}]
if some_condition > 0:
output_df = pd.DataFrame(list_of_dicts)
reveal_type(output_df)
output_df = output_df[["some", "columns", "here"]]
reveal_type(output_df)
else:
output_df = None
other_df: pd.DataFrame = output_df
return other_dfExpected Behavior
Each block and function should contain a mypy error regarding an incompatible assignment.
Actual Behavior
For the last function, no such error is produced:
mwe2.py:10: error: Incompatible types in assignment (expression has type "None", variable has type "DataFrame") [assignment]
mwe2.py:17: error: Incompatible types in assignment (expression has type "None", variable has type "DataFrame") [assignment]
mwe2.py:28: error: Incompatible types in assignment (expression has type "DataFrame | None", variable has type "DataFrame") [assignment]
mwe2.py:36: note: Revealed type is "pandas.core.frame.DataFrame"
mwe2.py:38: note: Revealed type is "pandas.core.frame.DataFrame"
Found 3 errors in 1 file (checked 1 source file)
Your Environment
- Mypy version used: mypy 1.14.1 (compiled: yes)
- Mypy command-line flags: only the filename
- Mypy configuration options from
pyproject.toml:
[tool.mypy]
warn_unreachable = true
enable_error_code = [
"possibly-undefined"
]
strict = true- Python version used: Python 3.12.8 from Debian Testing
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugmypy got something wrongmypy got something wronggood-second-issuetopic-type-narrowingConditional type narrowing / binderConditional type narrowing / binder
Activity
hauntsaninja commentedon Jan 20, 2025
Thanks! Pretty bad bug. Looks like it's just some issue if there's a double assignment (and specifically with None and without an explicit annotation, so likely related to partial types):
MaxG87 commentedon Jan 21, 2025
I can reproduce the minified example with the same toolchain as above.
[-]Incompatible Assignment of None to DataFrame in Function not Caught[/-][+]False narrowing with repeated assignment inside if resulting in bad false negative[/+]Prabhat-Thapa45 commentedon Jan 31, 2025
@hauntsaninja While trying to reproduce this I encountered an issue with mypy where it fails to correctly identify the type of variable and ultimately raises an error at an undesired place
east825 commentedon May 19, 2025
I've tried reproducing the original example and in the latest nightly (301c3b6) and it no longer reproduces. In the function
does_not_fail_but_shouldthere is a true positive error about assigning a value of typeDataFrame | Noneto a variable expectingDataFrame.The problem in the simplified example by @Prabhat-Thapa45 is still reproducible, though (mypy doesn't take into assignment a reachable assignment in the false branch).
However, with the experimental options
--allow-redefinition-new --local-partial-typesthe false negative in the simplified example disappears and the behavior in the first example becomes better. The reassignment ofoutput_dfon line 12 becomes possible and infails_as_expected_1mypy reports assignment of a None value toother_dfon line 20 instead of the reassigment ofoutput_dfon line 19.hauntsaninja commentedon May 19, 2025
Looks like this got fixed by #18538