diff --git a/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java b/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java index c03861b0331e..b755f3114455 100644 --- a/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java @@ -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(); diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 583c41c53777..5dcfc3381df2 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -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 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 diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 4fe2715fd4ed..65b454ac57b5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -765,7 +765,9 @@ public class PyClassImpl extends PyBaseElementImpl 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)) { diff --git a/python/testData/MockSdk2.7/Lib/abc.py b/python/testData/MockSdk2.7/Lib/abc.py index ef4f2f69e838..ec9311814a02 100644 --- a/python/testData/MockSdk2.7/Lib/abc.py +++ b/python/testData/MockSdk2.7/Lib/abc.py @@ -5,4 +5,8 @@ class ABCMeta: def abstractmethod(foo): + pass + + +def abstractproperty(foo): pass \ No newline at end of file diff --git a/python/testData/deprecation/abcAbstractProperty.py b/python/testData/deprecation/abcAbstractProperty.py index 341c23e24f96..f6511c793149 100644 --- a/python/testData/deprecation/abcAbstractProperty.py +++ b/python/testData/deprecation/abcAbstractProperty.py @@ -3,5 +3,32 @@ import abc class A(metaclass=abc.ABCMeta): @abc.abstractproperty + def prop(self): + pass + + +from abc import abstractproperty + + +class B(metaclass=abc.ABCMeta): + @abstractproperty + def prop(self): + pass + + +from abc import abstractproperty as ap + + +class C(metaclass=abc.ABCMeta): + @ap + def prop(self): + pass + + +import abc as foo + + +class D(metaclass=abc.ABCMeta): + @foo.abstractproperty def prop(self): pass \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/abcAbstractProperty.py b/python/testData/inspections/PyPropertyAccessInspection/abcAbstractProperty.py new file mode 100644 index 000000000000..a22163306c00 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/abcAbstractProperty.py @@ -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 \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyDefinitionInspection26/test.py b/python/testData/inspections/PyPropertyDefinitionInspection26/test.py index 6dd8927c9788..0d7736ab126d 100644 --- a/python/testData/inspections/PyPropertyDefinitionInspection26/test.py +++ b/python/testData/inspections/PyPropertyDefinitionInspection26/test.py @@ -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 diff --git a/python/testData/inspections/PyPropertyDefinitionInspection33/test.py b/python/testData/inspections/PyPropertyDefinitionInspection33/test.py index c34fd36671f4..aaf904a94de4 100644 --- a/python/testData/inspections/PyPropertyDefinitionInspection33/test.py +++ b/python/testData/inspections/PyPropertyDefinitionInspection33/test.py @@ -1,14 +1,17 @@ +import abc + + class A: @property def normal_property(self): pass @property - @abstractproperty + @abc.abstractproperty def abstract_property1(self): pass @property - @abstractmethod + @abc.abstractmethod def abstract_property2(self): pass diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 1e709aec7799..2f51068c9194 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -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", diff --git a/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java index 67848c045703..ac0a77e5aeb7 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java @@ -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");