From 49b4dec28161f4e8cd03dbf9c569322665108030 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Sat, 1 Jul 2017 17:11:36 +0300 Subject: [PATCH] PY-24930 Fixed: False negative: no parameter info when implicitly calling __call__ Update PyCallExpressionHelper to support a case when callee resolves to call expression and this call expression returns class with `__call__`. --- .../psi/impl/PyCallExpressionHelper.java | 20 ++++++++++++++++++- .../PyArgumentListInspection/callOperator.py | 7 +++++++ .../PyTypeCheckerInspection/CallOperator.py | 7 +++++++ python/testData/paramInfo/CallOperator.py | 7 +++++++ .../jetbrains/python/PyParameterInfoTest.java | 12 +++++++++++ .../PyArgumentListInspectionTest.java | 5 +++++ .../PyTypeCheckerInspectionTest.java | 5 +++++ 7 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyArgumentListInspection/callOperator.py create mode 100644 python/testData/inspections/PyTypeCheckerInspection/CallOperator.py create mode 100644 python/testData/paramInfo/CallOperator.py diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index bc967314fe81..012a89cecfec 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -196,7 +196,9 @@ public class PyCallExpressionHelper { function -> new ClarifiedResolveResult(resolveResult, function, null, true)); } else if (resolved instanceof PyCallExpression) { // foo = classmethod(foo) - final Pair wrapperInfo = interpretAsModifierWrappingCall((PyCallExpression)resolved); + final PyCallExpression resolvedCall = (PyCallExpression)resolved; + + final Pair wrapperInfo = interpretAsModifierWrappingCall(resolvedCall); if (wrapperInfo != null) { final String wrapperName = wrapperInfo.getFirst(); final PyFunction.Modifier wrappedModifier = PyNames.CLASSMETHOD.equals(wrapperName) @@ -208,6 +210,22 @@ public class PyCallExpressionHelper { final ClarifiedResolveResult result = new ClarifiedResolveResult(resolveResult, wrapperInfo.getSecond(), wrappedModifier, false); return Collections.singletonList(result); } + else { + final PyType resolvedCallType = resolveContext.getTypeEvalContext().getType(resolvedCall); + if (resolvedCallType instanceof PyClassLikeType) { + final List dunderCall = + ((PyClassLikeType)resolvedCallType).resolveMember(PyNames.CALL, resolvedCall, AccessDirection.READ, resolveContext, true); + + if (!ContainerUtil.isEmpty(dunderCall)) { + return StreamEx + .of(dunderCall) + .map(RatedResolveResult::getElement) + .nonNull() + .map(element -> new ClarifiedResolveResult(resolveResult, element, null, false)) + .toList(); + } + } + } } else if (resolved instanceof PyFunction) { final PyFunction function = (PyFunction)resolved; diff --git a/python/testData/inspections/PyArgumentListInspection/callOperator.py b/python/testData/inspections/PyArgumentListInspection/callOperator.py new file mode 100644 index 000000000000..21e1627d1312 --- /dev/null +++ b/python/testData/inspections/PyArgumentListInspection/callOperator.py @@ -0,0 +1,7 @@ +class Foo: + def __call__(self, arg: int): + return arg + +bar = Foo() +bar.__call__() +bar() \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/CallOperator.py b/python/testData/inspections/PyTypeCheckerInspection/CallOperator.py new file mode 100644 index 000000000000..432b51eddb77 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/CallOperator.py @@ -0,0 +1,7 @@ +class Foo: + def __call__(self, arg: int): + return arg + +bar = Foo() +bar.__call__("s") +bar("s") \ No newline at end of file diff --git a/python/testData/paramInfo/CallOperator.py b/python/testData/paramInfo/CallOperator.py new file mode 100644 index 000000000000..fa0709aa2fac --- /dev/null +++ b/python/testData/paramInfo/CallOperator.py @@ -0,0 +1,7 @@ +class Foo: + def __call__(self, arg: int): + return arg + +bar = Foo() +bar.__call__() +bar() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index b16b560c0411..f2ee9d920d55 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -696,6 +696,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase { ); } + // PY-24930 + public void testCallOperator() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> { + for (int offset : StreamEx.of(loadTest(2).values()).map(PsiElement::getTextOffset)) { + feignCtrlP(offset).check("self: Foo, arg: int", new String[]{"arg: int"}, new String[]{"self: Foo, "}); + } + } + ); + } + /** * Imitates pressing of Ctrl+P; fails if results are not as expected. * @param offset offset of 'cursor' where Ctrl+P is pressed. diff --git a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java index cf00761d87dc..ccf903d425b9 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java @@ -317,6 +317,11 @@ public class PyArgumentListInspectionTest extends PyTestCase { doTest(); } + // PY-24930 + public void testCallOperator() { + runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); + } + private void doMultiFileTest() { final String folderPath = "inspections/PyArgumentListInspection/" + getTestName(false) + "/"; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 3ba98ae8c05f..704fc4cf021c 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -417,4 +417,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testPromotingBytearrayToStrAndUnicode() { doTest(); } + + // PY-24930 + public void testCallOperator() { + runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); + } }