diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 2e6d4058ff76..6ee637fe6f10 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -100,8 +100,8 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i return describeDecorators(cls, LSame2, ", ", LSame1).add(describeClass(cls, LSame2, false, false)).toString() + "\n" + summary; } - else if (element instanceof PyTargetExpression || element instanceof PyNamedParameter) { - return describeExpression((PyExpression)element); + else if (element instanceof PyExpression) { + return describeExpression((PyExpression)element, originalElement); } return null; } @@ -131,7 +131,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i } @Nullable - private static String describeExpression(PyExpression expr) { + private static String describeExpression(@NotNull PyExpression expr, @NotNull PsiElement originalElement) { final String name = expr.getName(); if (name != null) { StringBuilder result = new StringBuilder((expr instanceof PyNamedParameter) ? "parameter" : "variable"); @@ -143,15 +143,17 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i result.append(String.format(" \"%s\"", function.getName())); } } - result.append("\n").append(describeExpressionType(expr)); + if (originalElement instanceof PyTypedElement) { + result.append("\n").append(describeType((PyTypedElement)originalElement)); + } return result.toString(); } return null; } - static String describeExpressionType(PyExpression expr) { - final TypeEvalContext context = TypeEvalContext.userInitiated(expr.getContainingFile()); - return String.format("Inferred type: %s", getTypeName(context.getType(expr), context)); + static String describeType(@NotNull PyTypedElement element) { + final TypeEvalContext context = TypeEvalContext.userInitiated(element.getContainingFile()); + return String.format("Inferred type: %s", getTypeName(context.getType(element), context)); } public static String getTypeDescription(@NotNull PyFunction fun) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 378ab8a676cc..c61693c075d7 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -400,23 +400,26 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp @Nullable private String extractReturnType() { + final String ARROW = "->"; + final StructuredDocString structuredDocString = getStructuredDocString(); + if (structuredDocString != null) { + return structuredDocString.getReturnType(); + } final String docString = getDocStringValue(); - if (docString == null) { - return null; - } - final List lines = StringUtil.split(docString, "\n"); - while (lines.size() > 0 && lines.get(0).trim().length() == 0) { - lines.remove(0); - } - if (lines.size() > 1 && lines.get(1).trim().length() == 0) { - String firstLine = lines.get(0); - int pos = firstLine.lastIndexOf("->"); - if (pos >= 0) { - return firstLine.substring(pos + 2).trim(); + if (docString != null && docString.contains(ARROW)) { + final List lines = StringUtil.split(docString, "\n"); + while (lines.size() > 0 && lines.get(0).trim().length() == 0) { + lines.remove(0); + } + if (lines.size() > 1 && lines.get(1).trim().length() == 0) { + String firstLine = lines.get(0); + int pos = firstLine.lastIndexOf(ARROW); + if (pos >= 0) { + return firstLine.substring(pos + 2).trim(); + } } } - final StructuredDocString structuredDocString = getStructuredDocString(); - return structuredDocString != null ? structuredDocString.getReturnType() : null; + return null; } private static class ReturnVisitor extends PyRecursiveElementVisitor { diff --git a/python/testData/quickdoc/HoverOverControlFlowUnion.html b/python/testData/quickdoc/HoverOverControlFlowUnion.html new file mode 100644 index 000000000000..056d6ba8ce3c --- /dev/null +++ b/python/testData/quickdoc/HoverOverControlFlowUnion.html @@ -0,0 +1,2 @@ +variable "x" +Inferred type: one of (str, int) diff --git a/python/testData/quickdoc/HoverOverControlFlowUnion.py b/python/testData/quickdoc/HoverOverControlFlowUnion.py new file mode 100644 index 000000000000..74ceb505df1b --- /dev/null +++ b/python/testData/quickdoc/HoverOverControlFlowUnion.py @@ -0,0 +1,6 @@ +def foo(c): + if c: + x = 'foo' + else: + x = 1 + return x \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 5227fc4c0521..de9ea7ff9c04 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -216,4 +216,8 @@ public class PyQuickDocTest extends LightMarkedTestCase { public void testHoverOverParameter() { checkHover(); } + + public void testHoverOverControlFlowUnion() { + checkHover(); + } } diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 3daeb4fd9c57..2348a1039500 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -629,6 +629,17 @@ public class PyTypeTest extends PyTestCase { assertTrue(PyTypeChecker.isUnknown(type)); } + public void testParameterOfFunctionTypeAndReturnValue() { + doTest("int", + "def func(f):\n" + + " '''\n" + + " :type f: (unknown) -> str\n" + + " '''\n" + + " return 1\n" + + "\n" + + "expr = func(foo)\n"); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing(); }