From 587cdcf35afdd07211a0ffe1a3ca682b0bbf7756 Mon Sep 17 00:00:00 2001 From: "evgeny.bovykin" Date: Mon, 16 Feb 2026 10:24:22 +0100 Subject: [PATCH] PY-64359 Upcast PyCollectionTypeImpl to Iterable to get its proper iterated item type (cherry picked from commit 2366c4b215a3631d270cd69850291da38e70b2ec) GitOrigin-RevId: 8175cdbea5ff6f05c5b1beaa0fa5fe21e03ec5cf --- .../python/psi/types/PyCollectionTypeImpl.java | 11 ++++++++++- python/testSrc/com/jetbrains/python/Py3TypeTest.java | 8 ++++---- python/testSrc/com/jetbrains/python/PyTypeTest.java | 6 +++--- .../inspections/Py3TypeCheckerInspectionTest.java | 8 ++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java index 729729b16da2..e3af1f7365b9 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java @@ -108,7 +108,16 @@ public class PyCollectionTypeImpl extends PyClassTypeImpl implements PyCollectio @Override public @Nullable PyType getIteratedItemType() { - // TODO: Select the parameter type that matches T in Iterable[T] + if (myElementTypes.size() >= 2) { + String iterableName = "typing.Iterable"; + if (!iterableName.equals(getClassQName())) { + TypeEvalContext context = TypeEvalContext.codeInsightFallback(getPyClass().getProject()); + PyType asIterable = PyTypeUtil.convertToType(this, iterableName, getPyClass(), context); + if (asIterable instanceof PyCollectionType collectionType) { + return collectionType.getIteratedItemType(); + } + } + } return ContainerUtil.getFirstItem(myElementTypes); } diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 56af668acfd5..e7d2559e28e0 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -477,7 +477,7 @@ public class Py3TypeTest extends PyTestCase { } public void testYieldFromUnknownTuple() { - doTest("Any", + doTest("_T_co", """ def get_tuple() -> tuple: pass @@ -487,7 +487,7 @@ public class Py3TypeTest extends PyTestCase { } public void testYieldFromUnknownList() { - doTest("Any", + doTest("_T_co", """ def get_list() -> list: pass @@ -497,7 +497,7 @@ public class Py3TypeTest extends PyTestCase { } public void testYieldFromUnknownDict() { - doTest("Any", + doTest("_T_co", """ def get_dict() -> dict: pass @@ -507,7 +507,7 @@ public class Py3TypeTest extends PyTestCase { } public void testYieldFromUnknownSet() { - doTest("Any", + doTest("_T_co", """ def get_set() -> set: pass diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 7d0040682c53..1145a0ed4f24 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1693,7 +1693,7 @@ public class PyTypeTest extends PyTestCase { // PY-20794 public void testIterateOverPureList() { - doTest("Any", + doTest("_T_co", """ l = None # type: list for expr in l: @@ -1703,7 +1703,7 @@ public class PyTypeTest extends PyTestCase { // PY-20794 public void testIterateOverDictValueWithDefaultValue() { - doTest("Any", + doTest("Union[_T_co, Any]", """ d = None # type: dict for expr in d.get('field', []): @@ -2300,7 +2300,7 @@ public class PyTypeTest extends PyTestCase { // PY-24364 public void testReassignedParameter() { - doTest("(entries: Any) -> Generator[Any, Any, None]", + doTest("(entries: Any) -> Generator[_T_co, Any, None]", """ def resort(entries): entries = list(entries) diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 1ece48adb205..b5ae56a3e990 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -4255,5 +4255,13 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase { serial_number = property(_get_serial_number) """); } + + // PY-64359 + public void testTupleDictValues() { + doTestByText(""" + def f(a: dict[str, int]): + b: tuple[int, ...] = tuple(a.values()) + """); + } }