Skip to content

False narrowing with repeated assignment inside if resulting in bad false negative #18492

Closed
@MaxG87

Description

@MaxG87

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_df

Expected 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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions