From 858597739405c7990ce7d216b651c3dc5576e129 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 27 Jul 2017 13:28:24 +0300 Subject: [PATCH] PY-18791 Fixed: False positive in type information when returning callable from property Consider property call in PyCallExpressionHelper.getCallTargetReturnType. --- .../psi/impl/PyCallExpressionHelper.java | 24 +++++++++++++++++++ .../com/jetbrains/python/PyTypeTest.java | 16 +++++++++++++ 2 files changed, 40 insertions(+) diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 4703a00ec7d1..d520c8386f05 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -584,6 +584,10 @@ public class PyCallExpressionHelper { if (providedType instanceof PyCallableType) { return Ref.create(((PyCallableType)providedType).getCallType(context, call)); } + final Ref propertyCallType = getPropertyCallType(call, target, context); + if (propertyCallType != null) { + return propertyCallType; + } if (target instanceof PyCallable) { final PyCallable callable = (PyCallable)target; return Ref.create(callable.getCallType(context, call)); @@ -591,6 +595,26 @@ public class PyCallExpressionHelper { return null; } + @Nullable + private static Ref getPropertyCallType(@NotNull PyCallExpression call, + @NotNull PsiElement target, + @NotNull TypeEvalContext context) { + if (target instanceof PyCallable && target instanceof PyPossibleClassMember) { + final PyClass containingClass = ((PyPossibleClassMember)target).getContainingClass(); + if (containingClass != null) { + final PyCallable callable = (PyCallable)target; + final Property property = containingClass.findPropertyByCallable(callable); + if (property != null) { + final PyType propertyType = property.getType(call.getReceiver(callable), context); + if (propertyType instanceof PyCallableType) { + return Ref.create(((PyCallableType)propertyType).getCallType(context, call)); + } + } + } + } + return null; + } + @NotNull private static Maybe getSuperCallType(@NotNull PyCallExpression call, TypeEvalContext context) { final PyExpression callee = call.getCallee(); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 589d7ef55154..2d807e1c6b75 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -2221,6 +2221,22 @@ public class PyTypeTest extends PyTestCase { ); } + // PY-18791 + public void testCallOnProperty() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> doTest("Iterator[int]", + "from typing import Iterator, Callable\n" + + "class Foo:\n" + + " def iterate(self) -> Iterator[int]:\n" + + " pass\n" + + " @property\n" + + " def foo(self) -> Callable[[], Iterator[int]]:\n" + + " return self.iterate\n" + + "expr = Foo().foo()") + ); + } + private static List getTypeEvalContexts(@NotNull PyExpression element) { return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(), TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());