From 31f33fd1756cac5b686332b251604bcac1d75e7f Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Wed, 28 Oct 2020 12:08:14 +0300 Subject: [PATCH] IDEA-CR-68177 PY-36610 Parameter info doesn't work in Python Console GitOrigin-RevId: f60d65180234eca86488aba5911b033333e04642 --- .../_pydev_bundle/_pydev_calltip_util.py | 5 ++--- .../psi/impl/PyCallExpressionHelper.java | 20 ++++++++++++++++--- .../psi/impl/PyReferenceExpressionImpl.java | 2 +- .../console/PydevConsoleCommunication.java | 2 +- .../jetbrains/env/debug/PyConsoleTask.java | 5 +++++ .../env/debug/PythonConsoleTest.java | 18 +++++++++++++++++ .../jetbrains/python/PyParameterInfoTest.java | 15 ++++++++++++-- 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py b/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py index b846fb4e5586..21618a34742d 100644 --- a/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py +++ b/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py @@ -46,7 +46,7 @@ def get_description(obj): if isinstance(obj, type) or type(obj).__name__ == 'classobj': fob = getattr(obj, '__init__', lambda: None) - if not isinstance(fob, (types.FunctionType, types.MethodType)): + if not callable(fob): fob = obj elif is_bound_method(ob_call): fob = ob_call @@ -54,9 +54,8 @@ def get_description(obj): fob = obj argspec = "" - fn_name = None fn_class = None - if isinstance(fob, (types.FunctionType, types.MethodType)): + if callable(fob): spec_info = inspect.getfullargspec(fob) if IS_PY3K else inspect.getargspec(fob) argspec = inspect.formatargspec(*spec_info) fn_name = getattr(fob, '__name__', None) diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index c7eea88cf925..8d288de0abbd 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -5,6 +5,7 @@ import com.intellij.codeInsight.completion.CompletionUtilCoreImpl; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiTreeUtil; @@ -12,6 +13,7 @@ import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.jetbrains.python.PyNames; +import com.jetbrains.python.PythonRuntimeService; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.references.PyReferenceImpl; @@ -104,8 +106,7 @@ public final class PyCallExpressionHelper { * please obtain its result via {@link TypeEvalContext#getType} with {@code call.getCallee()} as an argument. */ static @Nullable PyType getCalleeType(@NotNull PyCallExpression call, - @NotNull PyResolveContext resolveContext, - @SuppressWarnings("unused") @NotNull TypeEvalContext.Key key) { + @NotNull PyResolveContext resolveContext) { final List callableTypes = new ArrayList<>(); final TypeEvalContext context = resolveContext.getTypeEvalContext(); @@ -189,7 +190,10 @@ public final class PyCallExpressionHelper { return PyUtil.getParameterizedCachedValue( call, resolveContext, - it -> ContainerUtil.concat(getExplicitResolveResults(call, it), getImplicitResolveResults(call, it)) + it -> ContainerUtil.concat( + getExplicitResolveResults(call, it), + getImplicitResolveResults(call, it), + getRemoteResolveResults(call, it)) ); } @@ -277,6 +281,16 @@ public final class PyCallExpressionHelper { return Collections.emptyList(); } + @NotNull + private static List<@NotNull PyCallableType> getRemoteResolveResults(@NotNull PyCallExpression call, + @NotNull PyResolveContext resolveContext) { + if (!resolveContext.allowRemote()) return Collections.emptyList(); + PsiFile file = call.getContainingFile(); + if (file == null || !PythonRuntimeService.getInstance().isInPydevConsole(file)) return Collections.emptyList(); + PyType calleeType = getCalleeType(call, resolveContext); + return PyTypeUtil.toStream(calleeType).select(PyCallableType.class).toList(); + } + @NotNull private static List<@NotNull PyCallableType> selectCallableTypes(@NotNull List resolveResults, @NotNull TypeEvalContext context) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 54d65ed58499..238e5662387d 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -249,7 +249,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere private PyType getCallableType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) { PyCallExpression callExpression = PyCallExpressionNavigator.getPyCallExpressionByCallee(this); if (callExpression != null) { - return getCalleeType(callExpression, PyResolveContext.defaultContext().withTypeEvalContext(context), key); + return getCalleeType(callExpression, PyResolveContext.defaultContext().withTypeEvalContext(context)); } return null; } diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index a17c7fe486b2..b3dc68fad8c0 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -404,7 +404,7 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica // add temporary value to avoid repeated requests for the same expression myPrevNameToDescription = Pair.create(text, ""); } - if (ApplicationManager.getApplication().isDispatchThread()) { + if (ApplicationManager.getApplication().isDispatchThread() && !ApplicationManager.getApplication().isUnitTestMode()) { throw new PyDebuggerException("Documentation in Python Console shouldn't be called from Dispatch Thread!"); } diff --git a/python/testSrc/com/jetbrains/env/debug/PyConsoleTask.java b/python/testSrc/com/jetbrains/env/debug/PyConsoleTask.java index 8e0d38c45f94..49e1ae985ea2 100644 --- a/python/testSrc/com/jetbrains/env/debug/PyConsoleTask.java +++ b/python/testSrc/com/jetbrains/env/debug/PyConsoleTask.java @@ -18,6 +18,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; import com.intellij.util.ui.UIUtil; import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.env.PyExecutionFixtureTestTask; @@ -438,6 +439,10 @@ public class PyConsoleTask extends PyExecutionFixtureTestTask { }); } + public PsiFile getConsoleFile() { + return myConsoleView.getFile(); + } + @NotNull @Override public Set getTags() { diff --git a/python/testSrc/com/jetbrains/env/debug/PythonConsoleTest.java b/python/testSrc/com/jetbrains/env/debug/PythonConsoleTest.java index d8ea775c921e..479ea63b3fe2 100644 --- a/python/testSrc/com/jetbrains/env/debug/PythonConsoleTest.java +++ b/python/testSrc/com/jetbrains/env/debug/PythonConsoleTest.java @@ -3,6 +3,7 @@ package com.jetbrains.env.debug; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode; import com.jetbrains.env.PyEnvTestCase; import com.jetbrains.python.console.pydev.PydevCompletionVariant; @@ -20,6 +21,7 @@ import java.util.function.Predicate; import static com.intellij.testFramework.UsefulTestCase.assertContainsElements; import static com.jetbrains.env.debug.PyBaseDebuggerTask.findCompletionVariantByName; import static com.jetbrains.env.debug.PyBaseDebuggerTask.findDebugValueByName; +import static com.jetbrains.python.PyParameterInfoTest.checkParameters; import static org.junit.Assert.*; public class PythonConsoleTest extends PyEnvTestCase { @@ -284,4 +286,20 @@ public class PythonConsoleTest extends PyEnvTestCase { } }); } + + @Test + public void testParameterInfo() { + runPythonTest(new PyConsoleTask("/debug") { + @Override + public void testing() throws Exception { + exec("from os import getenv"); + exec("print(\"Hi\")"); + waitForOutput("Hi"); + addTextToEditor("getenv()"); + ApplicationManager.getApplication().invokeAndWait(() -> { + checkParameters(7, getConsoleFile(), "key, default=None", new String[]{"key, "}); + }); + } + }); + } } diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 5deb7205f33c..c1d2f47ec271 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -1150,16 +1150,22 @@ public class PyParameterInfoTest extends LightMarkedTestCase { ); } + @NotNull + private Collector feignCtrlP(int offset) { + return feignCtrlP(offset, myFixture.getFile()); + } + /** * Imitates pressing of Ctrl+P; fails if results are not as expected. + * * @param offset offset of 'cursor' where Ctrl+P is pressed. * @return a {@link Collector} with collected hint info. */ @NotNull - private Collector feignCtrlP(int offset) { + private static Collector feignCtrlP(int offset, @NotNull PsiFile file) { final PyParameterInfoHandler handler = new PyParameterInfoHandler(); - final Collector collector = new Collector(myFixture.getFile(), offset); + final Collector collector = new Collector(file, offset); collector.setParameterOwner(handler.findElementForParameterInfo(collector)); if (collector.getParameterOwner() != null) { @@ -1174,6 +1180,11 @@ public class PyParameterInfoTest extends LightMarkedTestCase { return collector; } + public static void checkParameters(int offset, @NotNull PsiFile file, @NotNull String text, String @NotNull [] highlighted) { + Collector collector = feignCtrlP(offset, file); + collector.check(text, highlighted); + } + /** * Imitates the normal UI contexts to the extent we use it. Collects highlighting. */