From 8fed6c91ee000c5a3e531850783ca26d1e25d95b Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 25 Sep 2013 17:29:14 +0400 Subject: [PATCH] Fixed function doesn't return anything inspection for decorated and overridden methods (PY-10883) --- .../PyNoneFunctionAssignmentInspection.java | 32 ++++++++++++++++--- .../decoratedMethod.py | 31 ++++++++++++++++++ .../methodWithInheritors.py | 15 +++++++++ ...yNoneFunctionAssignmentInspectionTest.java | 10 ++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/decoratedMethod.py create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/methodWithInheritors.py diff --git a/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java index 7ee80334961b..ea8dd58e8768 100644 --- a/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java @@ -3,11 +3,10 @@ package com.jetbrains.python.inspections; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElementVisitor; +import com.intellij.util.containers.hash.HashMap; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.psi.Callable; -import com.jetbrains.python.psi.PyAssignmentStatement; -import com.jetbrains.python.psi.PyCallExpression; -import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.psi.types.PyNoneType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.PyTypeChecker; @@ -16,6 +15,8 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + /** * User: ktisha * @@ -41,6 +42,8 @@ public class PyNoneFunctionAssignmentInspection extends PyInspection { private static class Visitor extends PyInspectionVisitor { + private final Map myHasInheritors = new HashMap(); + public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { super(holder, session); } @@ -56,11 +59,30 @@ public class PyNoneFunctionAssignmentInspection extends PyInspection { final PyTypeChecker.AnalyzeCallResults analyzeCallResults = PyTypeChecker.analyzeCall(((PyCallExpression)value), myTypeEvalContext); if (analyzeCallResults != null) { final Callable callable = analyzeCallResults.getCallable(); - if (PySdkUtil.isElementInSkeletons(callable)) return; + if (PySdkUtil.isElementInSkeletons(callable)) { + return; + } + if (callable instanceof PyFunction) { + final PyFunction function = (PyFunction)callable; + // Currently we don't infer types returned by decorators + if (hasInheritors(function) || PyUtil.hasCustomDecorators(function)) { + return; + } + } registerProblem(node, PyBundle.message("INSP.none.function.assignment", callee.getName())); } } } } + + private boolean hasInheritors(@NotNull PyFunction function) { + final Boolean cached = myHasInheritors.get(function); + if (cached != null) { + return cached; + } + final boolean result = PyOverridingMethodsSearch.search(function, true).findFirst() != null; + myHasInheritors.put(function, result); + return result; + } } } diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/decoratedMethod.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/decoratedMethod.py new file mode 100644 index 000000000000..cb6aa641838e --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/decoratedMethod.py @@ -0,0 +1,31 @@ +import abc +from abc import abstractmethod + + +def decorator(f): + return f + + +class C(object): + __metaclass__ = abc.ABCMeta + + @abstractmethod + def foo(self): + pass + + @abc.abstractmethod + def bar(self): + pass + + @decorator + def baz(self): + pass + + def quux(self): + pass + + def test(self): + a = self.foo() + b = self.bar() + c = self.baz() + d = self.quux() diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/methodWithInheritors.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/methodWithInheritors.py new file mode 100644 index 000000000000..e2bd1ebc9331 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/methodWithInheritors.py @@ -0,0 +1,15 @@ +class C(object): + def foo(self): + pass + + def bar(self): + pass + + def test(self): + x = self.foo() + y = self.bar() + + +class D(C): + def foo(self): + return 2 diff --git a/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java index 7247cc07afe6..5400b1116576 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java @@ -27,6 +27,16 @@ public class PyNoneFunctionAssignmentInspectionTest extends PyTestCase { doTest(); } + // PY-10883 + public void testMethodWithInheritors() { + doTest(); + } + + // PY-10883 + public void testDecoratedMethod() { + doTest(); + } + private void doTest() { myFixture.configureByFile("inspections/PyNoneFunctionAssignmentInspection/" + getTestName(true) + ".py"); myFixture.enableInspections(PyNoneFunctionAssignmentInspection.class);