diff --git a/mypy/checker.py b/mypy/checker.py index 4d4f376f25dd..90008bf10c4e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6778,6 +6778,17 @@ def comparison_type_narrowing_helper(self, node: ComparisonExpr) -> tuple[TypeMa expr_indices=[0, 1], narrowable_indices={0}, ) + if else_map and not ( + isinstance(p_typ := get_proper_type(iterable_type), TupleType) + and all( + is_singleton_equality_type(get_proper_type(item)) + for item in p_typ.items + ) + ): + # In general, we can't do negative narrowing, since e.g. the container + # could just be empty. However, we can do negative narrowing for some + # tuples e.g. `x not in (None,)` + else_map = {} if right_index in narrowable_operand_index_to_hash: if_type, else_type = self.conditional_types_for_iterable( diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 6ae8a3ae11d8..e02d81afed6d 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3268,6 +3268,21 @@ def f1(x: Union[str, float], t1: list[Literal['a', 'b']], t2: list[str]): reveal_type(x) # N: Revealed type is "builtins.str | builtins.float" [builtins fixtures/primitives.pyi] +[case testNegativeNarrowingInContainer] +# flags: --warn-unreachable +from enum import Enum + +class Color(Enum): + RED = 1 + +def count(colors: list[Color]) -> None: + counts: dict[Color, int] = {} + for color in colors: + if color not in counts: + counts[color] = 0 + counts[color] += 1 +[builtins fixtures/dict.pyi] + [case testNarrowAnyWithEqualityOrContainment] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/17841