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__`.
This commit is contained in:
Semyon Proshev
2017-07-03 17:50:38 +03:00
parent ce1432abc6
commit 49b4dec281
7 changed files with 62 additions and 1 deletions
@@ -196,7 +196,9 @@ public class PyCallExpressionHelper {
function -> new ClarifiedResolveResult(resolveResult, function, null, true));
}
else if (resolved instanceof PyCallExpression) { // foo = classmethod(foo)
final Pair<String, PyFunction> wrapperInfo = interpretAsModifierWrappingCall((PyCallExpression)resolved);
final PyCallExpression resolvedCall = (PyCallExpression)resolved;
final Pair<String, PyFunction> 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<? extends RatedResolveResult> 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;
@@ -0,0 +1,7 @@
class Foo:
def __call__(self, arg: int):
return arg
bar = Foo()
bar.__call__(<warning descr="Parameter 'arg' unfilled">)</warning>
bar(<warning descr="Parameter 'arg' unfilled">)</warning>
@@ -0,0 +1,7 @@
class Foo:
def __call__(self, arg: int):
return arg
bar = Foo()
bar.__call__(<warning descr="Expected type 'int', got 'str' instead">"s"</warning>)
bar(<warning descr="Expected type 'int', got 'str' instead">"s"</warning>)
@@ -0,0 +1,7 @@
class Foo:
def __call__(self, arg: int):
return arg
bar = Foo()
bar.__call__(<arg1>)
bar(<arg2>)
@@ -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.
@@ -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) + "/";
@@ -417,4 +417,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
public void testPromotingBytearrayToStrAndUnicode() {
doTest();
}
// PY-24930
public void testCallOperator() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
}
}