From e033f127a6d9e5e0ae5ddfd3356e926c4eb619de Mon Sep 17 00:00:00 2001 From: apoorva-01 Date: Sun, 12 Jul 2026 12:22:02 +0530 Subject: [PATCH] Infer yield-from item type for concrete Iterable subclasses get_generator_yield_type fell back to Any for a yield from over a user-defined Iterator subclass, hiding type mismatches; map it to Iterable like a for loop. Fixes #17449. --- mypy/checkexpr.py | 14 +++++++++++++- test-data/unit/check-statements.test | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 172d44555b946..ded361bb04613 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -6413,7 +6413,19 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # Check that the iterator's item type matches the type yielded by the Generator function # containing this `yield from` expression. expected_item_type = self.chk.get_generator_yield_type(return_type, False) - actual_item_type = self.chk.get_generator_yield_type(iter_type, False) + proper_iter_type = get_proper_type(iter_type) + if ( + isinstance(proper_iter_type, Instance) + and not self.chk.is_generator_return_type(proper_iter_type, is_coroutine=False) + and not self.chk.is_async_generator_return_type(proper_iter_type) + and self.chk.type_is_iterable(proper_iter_type) + ): + # A concrete Iterable/Iterator subclass isn't one of the generator types, and its item + # type isn't necessarily its first type argument (e.g. enumerate), so map it to Iterable + # the way a `for` loop does instead of falling back to Any. + actual_item_type = self.chk.iterable_item_type(proper_iter_type, e) + else: + actual_item_type = self.chk.get_generator_yield_type(iter_type, False) self.chk.check_subtype( actual_item_type, diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index cb445cf4a1a2b..1a8204b14f811 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -1349,6 +1349,32 @@ def f() -> Iterator[str]: yield from h() # E: Incompatible types in "yield from" (actual type "int", expected type "str") [out] +[case testYieldFromIteratorSubclass] +# https://github.com/python/mypy/issues/17449 +from typing import Iterator, Tuple, TypeVar +T = TypeVar("T") + +class Ints(Iterator[int]): + def __next__(self) -> int: ... + def __iter__(self) -> "Ints": ... + +def bad() -> Iterator[str]: + yield from Ints() # E: Incompatible types in "yield from" (actual type "int", expected type "str") +def good() -> Iterator[int]: + yield from Ints() + +# The item type is not the first type argument, so a naive args[0] would be wrong here. +class Pairs(Iterator[Tuple[int, T]]): + def __next__(self) -> Tuple[int, T]: ... + def __iter__(self) -> "Pairs[T]": ... + +def pairs_good() -> Iterator[Tuple[int, str]]: + yield from Pairs[str]() +def pairs_bad() -> Iterator[str]: + yield from Pairs[str]() # E: Incompatible types in "yield from" (actual type "tuple[int, str]", expected type "str") +[builtins fixtures/tuple.pyi] +[out] + [case testYieldFromAppliedToAny] from typing import Any def g() -> Any: