PY-20322 Fixed: False positive warnings about abstract property

Use PyKnownDecoratorUtil to check if the specified decorator is one of the known decorators
This commit is contained in:
Semyon Proshev
2017-02-14 19:19:47 +03:00
committed by Semyon Proshev
parent 6d223ec43c
commit c55d28bc8b
10 changed files with 116 additions and 28 deletions
@@ -89,7 +89,7 @@ public class PyDeprecationInspection extends PyInspection {
if (LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON33) && decoratorList != null) {
Arrays
.stream(decoratorList.getDecorators())
.filter(decorator -> KnownDecorator.ABC_ABSTRACTPROPERTY.getQualifiedName().equals(decorator.getQualifiedName()))
.filter(decorator -> PyKnownDecoratorUtil.asKnownDecorator(decorator, myTypeEvalContext) == KnownDecorator.ABC_ABSTRACTPROPERTY)
.forEach(
decorator -> {
final QualifiedName abcAbsPropertyQName = KnownDecorator.ABC_ABSTRACTPROPERTY.getQualifiedName();
@@ -22,6 +22,7 @@ import com.intellij.psi.util.QualifiedName;
import com.intellij.util.containers.Convertor;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.types.TypeEvalContext;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,34 +124,41 @@ public class PyKnownDecoratorUtil {
if (decoratorList == null) {
return Collections.emptyList();
}
final List<KnownDecorator> result = new ArrayList<>();
final boolean allowResolve = context.maySwitchToAST((PsiElement)element);
for (PyDecorator decorator : decoratorList.getDecorators()) {
final QualifiedName qualifiedName = decorator.getQualifiedName();
if (qualifiedName == null) {
continue;
return StreamEx
.of(decoratorList.getDecorators())
.map(decorator -> asKnownDecorator(decorator, context))
.nonNull()
.toList();
}
@Nullable
public static KnownDecorator asKnownDecorator(@NotNull PyDecorator decorator, @NotNull TypeEvalContext context) {
final QualifiedName qualifiedName = decorator.getQualifiedName();
if (qualifiedName == null) {
return null;
}
if (context.maySwitchToAST(decorator)) {
PyQualifiedNameOwner resolved = as(resolveDecorator(decorator), PyQualifiedNameOwner.class);
if (resolved instanceof PyFunction && PyNames.INIT.equals(resolved.getName())) {
resolved = ((PyFunction)resolved).getContainingClass();
}
final KnownDecorator knownDecorator = ourByShortName.get(qualifiedName.getLastComponent());
if (knownDecorator != null) {
if (allowResolve) {
PyQualifiedNameOwner resolved = as(resolveDecorator(decorator), PyQualifiedNameOwner.class);
if (resolved instanceof PyFunction && PyNames.INIT.equals(resolved.getName())) {
resolved = ((PyFunction)resolved).getContainingClass();
}
if (resolved != null && resolved.getQualifiedName() != null) {
final QualifiedName resolvedName = QualifiedName.fromDottedString(resolved.getQualifiedName());
if (resolvedName.equals(knownDecorator.getQualifiedName())) {
result.add(knownDecorator);
}
}
}
else {
result.add(knownDecorator);
if (resolved != null && resolved.getQualifiedName() != null) {
final QualifiedName resolvedName = QualifiedName.fromDottedString(resolved.getQualifiedName());
final KnownDecorator knownDecorator = ourByShortName.get(resolvedName.getLastComponent());
if (knownDecorator != null && resolvedName.equals(knownDecorator.getQualifiedName())) {
return knownDecorator;
}
}
}
return result;
else {
return ourByShortName.get(qualifiedName.getLastComponent());
}
return null;
}
@Nullable
@@ -765,7 +765,9 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
}
}
if (PyNames.PROPERTY.equals(decoName) ||
qname.equals(PyKnownDecoratorUtil.KnownDecorator.ABC_ABSTRACTPROPERTY.getQualifiedName())) {
ArrayUtil.contains(PyKnownDecoratorUtil.asKnownDecorator(deco, TypeEvalContext.codeInsightFallback(getProject())),
PyKnownDecoratorUtil.KnownDecorator.ABC_ABSTRACTPROPERTY,
PyKnownDecoratorUtil.KnownDecorator.PROPERTY)) {
getter = new Maybe<>(method);
}
else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.GETTER)) {
+4
View File
@@ -5,4 +5,8 @@ class ABCMeta:
def abstractmethod(foo):
pass
def abstractproperty(foo):
pass
@@ -3,5 +3,32 @@ import abc
class A(metaclass=abc.ABCMeta):
<warning descr="'abc.abstractproperty' is deprecated since Python 3.3. Use 'property' with 'abc.abstractmethod' instead.">@abc.abstractproperty</warning>
def prop(self):
pass
from abc import abstractproperty
class B(metaclass=abc.ABCMeta):
<warning descr="'abc.abstractproperty' is deprecated since Python 3.3. Use 'property' with 'abc.abstractmethod' instead.">@abstractproperty</warning>
def prop(self):
pass
from abc import abstractproperty as ap
class C(metaclass=abc.ABCMeta):
<warning descr="'abc.abstractproperty' is deprecated since Python 3.3. Use 'property' with 'abc.abstractmethod' instead.">@ap</warning>
def prop(self):
pass
import abc as foo
class D(metaclass=abc.ABCMeta):
<warning descr="'abc.abstractproperty' is deprecated since Python 3.3. Use 'property' with 'abc.abstractmethod' instead.">@foo.abstractproperty</warning>
def prop(self):
pass
@@ -0,0 +1,15 @@
from abc import ABCMeta, abstractproperty
class Upper(object):
__metaclass__ = ABCMeta
@abstractproperty
def prop(self):
pass
@prop.setter
def prop(self, prop):
pass
def foo(self):
print self.prop
@@ -1,3 +1,6 @@
import abc
class A(object):
def __init__(self):
self._x = 1
@@ -65,7 +68,7 @@ class A(object):
foo2 = property(get_foo2)
@property
@abstractproperty
@abc.abstractproperty
def abstract_property(self):
pass
@@ -1,14 +1,17 @@
import abc
class A:
@property
def <warning descr="Getter should return or yield something">normal_property</warning>(self):
pass
@property
@abstractproperty
@abc.abstractproperty
def abstract_property1(self):
pass
@property
@abstractmethod
@abc.abstractmethod
def abstract_property2(self):
pass
@@ -1472,6 +1472,27 @@ public class PyTypeTest extends PyTestCase {
"expr = D().foo");
}
public void testAbsAbstractPropertyWithFrom() {
doTest("str",
"from abc import abstractproperty\n" +
"class D:\n" +
" @abstractproperty\n" +
" def foo(self):\n" +
" return 'foo'\n" +
"expr = D().foo");
}
// TODO: enable this test when properties will be calculated with TypeEvalContext
public void ignoredTestAbsAbstractPropertyWithAs() {
doTest("str",
"from abc import abstractproperty as ap\n" +
"class D:\n" +
" @ap\n" +
" def foo(self):\n" +
" return 'foo'\n" +
"expr = D().foo");
}
// PY-20409
public void testGetFromDictWithDefaultNoneValue() {
doTest("Any",
@@ -31,6 +31,11 @@ public class PyPropertyAccessInspectionTest extends PyTestCase {
doTest();
}
// PY-20322
public void testAbcAbstractProperty() {
doTest();
}
private void doTest() {
setLanguageLevel(LanguageLevel.PYTHON26);
myFixture.configureByFile("inspections/PyPropertyAccessInspection/" + getTestName(true) + ".py");