From d75f031428609d052e18f56ee3bba2c6afc6fd95 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 15 Aug 2014 19:44:39 +0400 Subject: [PATCH] PY-9342 Follow assignments chain to determine that method is bound --- .../python/psi/types/PyFunctionType.java | 15 ++++++++++- .../reassignedMethodSpecialAttributes.py | 6 +++++ .../methodSpecialAttributes.py | 25 +++++++++++++++++++ .../python/PythonCompletionTest.java | 5 ++++ .../PyUnresolvedReferencesInspectionTest.java | 5 ++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 python/testData/completion/reassignedMethodSpecialAttributes.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index 2c804260c704..4bdbae69d83d 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -18,10 +18,12 @@ package com.jetbrains.python.psi.types; import com.intellij.psi.PsiElement; import com.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.resolve.RatedResolveResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -116,12 +118,23 @@ public class PyFunctionType implements PyCallableType { final PyFunction function = as(getCallable(), PyFunction.class); final boolean isNonStaticMethod = function != null && function.getContainingClass() != null && function.getModifier() != STATICMETHOD; if (isNonStaticMethod) { + // In Python 2 unbound methods have __method fake type if (LanguageLevel.forElement(location).isOlderThan(LanguageLevel.PYTHON30)) { return true; } + final PyExpression qualifier; if (location.isQualified()) { + qualifier = location.getQualifier(); + } + else { + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + final QualifiedResolveResult resolveResult = location.followAssignmentsChain(resolveContext); + final List qualifiers = resolveResult.getQualifiers(); + qualifier = ContainerUtil.isEmpty(qualifiers) ? null : qualifiers.get(qualifiers.size() - 1); + } + if (qualifier != null) { //noinspection ConstantConditions - final PyType qualifierType = PyTypeChecker.toNonWeakType(context.getType(location.getQualifier()), context); + final PyType qualifierType = PyTypeChecker.toNonWeakType(context.getType(qualifier), context); if (isInstanceType(qualifierType)) { return true; } diff --git a/python/testData/completion/reassignedMethodSpecialAttributes.py b/python/testData/completion/reassignedMethodSpecialAttributes.py new file mode 100644 index 000000000000..faf25e0022c7 --- /dev/null +++ b/python/testData/completion/reassignedMethodSpecialAttributes.py @@ -0,0 +1,6 @@ +class MyClass(object): + def method(self): + pass + +m = MyClass().method +m.__ \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py b/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py new file mode 100644 index 000000000000..7a60e533a15d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/methodSpecialAttributes.py @@ -0,0 +1,25 @@ +class MyClass(object): + def method(self): + pass + + @staticmethod + def static_method(): + pass + + +# Unbound method still treated as __method in Python 2 +MyClass.method.__func__ +MyClass.method.__defaults__ + +# Bound method with qualifier +inst = MyClass() +inst.method.__func__ +inst.method.__defaults__ + +# Reassigned bound method without qualifier +m = inst.method + +# Static method +# This reference should be marked as unresolved, but such warnings are suppressed for methods with decorators +inst.static_method.__func__ +inst.static_method.__defaults__ diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index e47171c856b8..a1366ac2e17a 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -694,6 +694,11 @@ public class PythonCompletionTest extends PyTestCase { assertUnderscoredFunctionAttributesSuggested(); } + // PY-9342 + public void testReassignedMethodSpecialAttributes() { + assertUnderscoredMethodSpecialAttributesSuggested(); + } + private void assertUnderscoredFunctionAttributesSuggested() { myFixture.configureByFile("completion/" + getTestName(true) + ".py"); myFixture.completeBasic(); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index a0a2a1232b2e..3451712f6bcd 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -371,6 +371,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doMultiFileTest(); } + // PY-9342 + public void testMethodSpecialAttributes() { + doTest(); + } + // PY-11472 public void testUnusedImportBeforeStarImport() { doMultiFileTest();