diff --git a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java index f2ba8501238a..271930390272 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java @@ -236,6 +236,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getType(@NotNull PyExpression expression, @NotNull TypeEvalContext context) { final PsiElement resolved = tryResolving(expression, context); + return getTypeForResolvedElement(resolved, context); + } + + @Nullable + private static PyType getTypeForResolvedElement(@NotNull PsiElement resolved, @NotNull TypeEvalContext context) { final PyType unionType = getUnionType(resolved, context); if (unionType != null) { return unionType; @@ -294,6 +299,13 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return null; } + @Nullable + public static PyType getTypeFromTargetExpression(@NotNull PyTargetExpression expression, @NotNull TypeEvalContext context) { + // XXX: Requires switching from stub to AST + final PyExpression assignedValue = expression.findAssignedValue(); + return assignedValue != null ? getTypeForResolvedElement(assignedValue, context) : null; + } + @Nullable private static Ref getClassType(@NotNull PsiElement element, @NotNull TypeEvalContext context) { if (element instanceof PyTypedElement) { diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index e516aef231cb..2a585d743ae2 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -441,31 +441,34 @@ public class PyTypeParser { PsiElement resolved = file.getElementNamed(firstText); if (resolved != null) { // Local or imported name - if (resolved instanceof PyTypedElement) { + if (resolved instanceof PyTargetExpression) { + type = PyTypingTypeProvider.getTypeFromTargetExpression((PyTargetExpression)resolved, context); + } + if (type == null && resolved instanceof PyTypedElement) { type = context.getType((PyTypedElement)resolved); - if (type != null) { - tokens.remove(0); - if (!allowResolveToType(type)) { - return null; - } - if (type instanceof PyClassLikeType) { - type = ((PyClassLikeType)type).toInstance(); - } - types.put(firstRange, type); - fullRanges.put(type, firstRange); - for (PyFromImportStatement fromImportStatement : file.getFromImports()) { - for (PyImportElement importElement : fromImportStatement.getImportElements()) { - if (firstText.equals(importElement.getVisibleName())) { - imports.put(type, importElement); - } - } - } - for (PyImportElement importElement : file.getImportTargets()) { + } + if (type != null) { + tokens.remove(0); + if (!allowResolveToType(type)) { + return null; + } + if (type instanceof PyClassLikeType) { + type = ((PyClassLikeType)type).toInstance(); + } + types.put(firstRange, type); + fullRanges.put(type, firstRange); + for (PyFromImportStatement fromImportStatement : file.getFromImports()) { + for (PyImportElement importElement : fromImportStatement.getImportElements()) { if (firstText.equals(importElement.getVisibleName())) { imports.put(type, importElement); } } } + for (PyImportElement importElement : file.getImportTargets()) { + if (firstText.equals(importElement.getVisibleName())) { + imports.put(type, importElement); + } + } } } else { @@ -497,7 +500,8 @@ public class PyTypeParser { } private static boolean allowResolveToType(@NotNull PyType type) { - return type instanceof PyClassLikeType || type instanceof PyModuleType || type instanceof PyImportedModuleType; + return type instanceof PyClassLikeType || type instanceof PyModuleType || type instanceof PyImportedModuleType || + type instanceof PyGenericType; } @Nullable diff --git a/python/testData/quickdoc/NumPyOnesDoc.html b/python/testData/quickdoc/NumPyOnesDoc.html index 2f67013327cd..efe990b3cded 100644 --- a/python/testData/quickdoc/NumPyOnesDoc.html +++ b/python/testData/quickdoc/NumPyOnesDoc.html @@ -1,4 +1,4 @@ def ones(shape, dtype=None, order='C') -Inferred type: (shape: Union[int, Iterable[int]], dtype: Optional[object], order: Optional[str]) -> ndarray
+Inferred type: (shape: Union[int, Iterable[int]], dtype: Optional[object], order: Optional[str]) -> ndarray
**Test docstring** Return a new array of given shape and type, filled with ones. diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 50cd09d9c7f7..2411e2ef0478 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -377,6 +377,48 @@ public class PyTypingTest extends PyTestCase { " pass\n"); } + // PY-16303 + public void testAssignedTypeInDocstring() { + doTest("list[int]", + "from typing import List\n" + + "\n" + + "IntList = List[int]\n" + + "\n" + + "def foo(expr):\n" + + " '''\n" + + " :type expr: IntList\n" + + " '''\n" + + " pass\n"); + } + + // PY-16303 + public void testParameterAssignedTypeInDocstring() { + doTest("Union[int, list[int]]", + "from typing import List, Union\n" + + "\n" + + "IntList = List[int]\n" + + "\n" + + "def foo(expr):\n" + + " '''\n" + + " :type expr: Union[int, IntList]\n" + + " '''\n" + + " pass\n"); + } + + // PY-16303 + public void testTypeVarInDocstring() { + doTest("TypeVar('TV')", + "from typing import TypeVar\n" + + "\n" + + "TV = TypeVar('TV')\n" + + "\n" + + "def foo(expr):\n" + + " '''\n" + + " :type expr: TV\n" + + " '''\n" + + " pass\n"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());