From 03c806c2f07c344455589b60fd22246109b43187 Mon Sep 17 00:00:00 2001 From: w-Jessamine Date: Mon, 29 Jun 2026 21:26:07 +0800 Subject: [PATCH 1/2] Fix iterable membership with subclass item types --- mypy/checkexpr.py | 9 ++++++++- test-data/unit/check-expressions.test | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 44855f49afaf9..f7ef2c7562168 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3801,7 +3801,14 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: if not encountered_partial_type and not failed_out: iterable_type = UnionType.make_union(iterable_types) - if not is_subtype(left_type, iterable_type): + matches_iterable_item = False + if iterable_types: + left_item_type = remove_instance_last_known_values(left_type) + erased_iterable_type = remove_instance_last_known_values(iterable_type) + matches_iterable_item = is_subtype( + left_item_type, erased_iterable_type + ) or is_subtype(erased_iterable_type, left_item_type) + if not matches_iterable_item: if not container_types: self.msg.unsupported_operand_types("in", left_type, right_type, e) else: diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 7123285e5eca1..a5bde3fa1346c 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -412,6 +412,20 @@ class D(Iterable[A]): def __iter__(self) -> Iterator[A]: pass [builtins fixtures/bool.pyi] +[case testInOperatorWithOverlappingIterableItemType] +from typing import Iterator + +class Number(int): pass + +def numbers() -> Iterator[Number]: + yield Number() + +1 in numbers() +Number() in numbers() +'' in numbers() # E: Unsupported operand types for in ("str" and "Iterator[Number]") +[builtins fixtures/primitives.pyi] +[typing fixtures/typing-medium.pyi] + [case testNotInOperator] from typing import Iterator, Iterable, Any a: A From aecdfb119723227acbf1c0198ce6f6bc6d0e3dc6 Mon Sep 17 00:00:00 2001 From: w-Jessamine Date: Mon, 29 Jun 2026 21:57:33 +0800 Subject: [PATCH 2/2] Preserve literal membership diagnostics --- mypy/checkexpr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f7ef2c7562168..3ad253597df74 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3807,7 +3807,10 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: erased_iterable_type = remove_instance_last_known_values(iterable_type) matches_iterable_item = is_subtype( left_item_type, erased_iterable_type - ) or is_subtype(erased_iterable_type, left_item_type) + ) or ( + not is_literal_type_like(left_type) + and is_subtype(erased_iterable_type, left_item_type) + ) if not matches_iterable_item: if not container_types: self.msg.unsupported_operand_types("in", left_type, right_type, e)