PY-9342 Follow assignments chain to determine that method is bound

This commit is contained in:
Mikhail Golubev
2014-08-18 13:15:04 +04:00
parent a1ae56b0f9
commit d75f031428
5 changed files with 55 additions and 1 deletions
@@ -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<PyExpression> 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;
}
@@ -0,0 +1,6 @@
class MyClass(object):
def method(self):
pass
m = MyClass().method
m.__<caret>
@@ -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.<warning descr="Cannot find reference '__defaults__' in 'function'">__defaults__</warning>
# Bound method with qualifier
inst = MyClass()
inst.method.__func__
inst.method.<warning descr="Cannot find reference '__defaults__' in 'function'">__defaults__</warning>
# 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__
@@ -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();
@@ -371,6 +371,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doMultiFileTest();
}
// PY-9342
public void testMethodSpecialAttributes() {
doTest();
}
// PY-11472
public void testUnusedImportBeforeStarImport() {
doMultiFileTest();