diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index d3870979e5bd..896038fa6d1b 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -303,7 +303,7 @@ public class PyTypeChecker { } else if (type instanceof PyTupleType) { final PyTupleType tuple = (PyTupleType)type; - final int n = tuple.getElementCount(); + final int n = tuple.isHomogeneous() ? 1 : tuple.getElementCount(); for (int i = 0; i < n; i++) { collectGenerics(tuple.getElementType(i), context, collected, visited); } @@ -349,13 +349,19 @@ public class PyTypeChecker { } else if (type instanceof PyTupleType) { final PyTupleType tuple = (PyTupleType)type; - final int n = tuple.getElementCount(); - final List results = new ArrayList<>(); - for (int i = 0; i < n; i++) { - final PyType subst = substitute(tuple.getElementType(i), substitutions, context); - results.add(subst); + + if (tuple.isHomogeneous()) { + return PyTupleType.createHomogeneous(tuple.getPyClass(), substitute(tuple.getElementType(0), substitutions, context)); + } else { + final int elementCount = tuple.getElementCount(); + final PyType[] elementTypes = new PyType[elementCount]; + + for (int i = 0; i < elementCount; i++) { + elementTypes[i] = substitute(tuple.getElementType(i), substitutions, context); + } + + return PyTupleType.create(tuple.getPyClass(), elementTypes); } - return new PyTupleType((PyTupleType)type, results.toArray(new PyType[results.size()])); } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; @@ -465,7 +471,7 @@ public class PyTypeChecker { if (callSite instanceof PyCallExpression) { final PyCallExpression callExpr = (PyCallExpression)callSite; final PyCallExpression.PyMarkedCallee callee = callExpr.resolveCallee(resolveContext); - return callee != null ? Collections.singletonList(callee.getCallable()) : Collections.emptyList(); + return callee != null ? Collections.singletonList(callee.getCallable()) : Collections.emptyList(); } else if (callSite instanceof PySubscriptionExpression || callSite instanceof PyBinaryExpression) { final List results = new ArrayList<>(); @@ -484,7 +490,7 @@ public class PyTypeChecker { } resolvedToUnknownResult = true; } - return resolvedToUnknownResult ? Collections.emptyList() : results; + return resolvedToUnknownResult ? Collections.emptyList() : results; } else { return Collections.emptyList(); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 09bf4fd3bf0c..6148279f66f7 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -886,6 +886,44 @@ public class PyTypeTest extends PyTestCase { " expr, foo = xs\n"); } + public void testHomogeneousTupleSubstitution() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> { + myFixture.copyDirectoryToProject("typing", ""); + + doTest("Tuple[int, ...]", + "from typing import TypeVar, Tuple\n" + + "T = TypeVar('T')\n" + + "def foo(i: T) -> Tuple[T, ...]:\n" + + " pass\n" + + "expr = foo(5)"); + } + ); + } + + public void testHeterogeneousTupleSubstitution() { + doTest("tuple[int, int]", + "def foo(i):\n" + + " \"\"\"\n" + + " :type i: T\n" + + " :rtype: tuple[T, T]\n" + + " \"\"\"\n" + + " pass\n" + + "expr = foo(5)"); + } + + public void testUnknownTupleSubstitution() { + doTest("tuple", + "def foo(i):\n" + + " \"\"\"\n" + + " :type i: T\n" + + " :rtype: tuple\n" + + " \"\"\"\n" + + " pass\n" + + "expr = foo(5)"); + } + public void testTupleIterationType() { doTest("Union[int, str]", "xs = (1, 'a')\n" +