diff --git a/python/psi-api/src/com/jetbrains/python/psi/Property.java b/python/psi-api/src/com/jetbrains/python/psi/Property.java index aad101b0a575..60373dd0a0db 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/Property.java +++ b/python/psi-api/src/com/jetbrains/python/psi/Property.java @@ -1,5 +1,7 @@ package com.jetbrains.python.psi; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,4 +53,9 @@ public interface Property { @NotNull Maybe getByDirection(@NotNull AccessDirection direction); + /** + * Get the return type of the property getter. + */ + @Nullable + PyType getType(@NotNull TypeEvalContext context); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 1eb2a9b1fcb4..829cde42f98e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -691,6 +691,24 @@ public class PyClassImpl extends PyPresentableElementImpl implement mySite = site; } + @NotNull + @Override + public Maybe getGetter() { + return filterNonStubExpression(myGetter); + } + + @NotNull + @Override + public Maybe getSetter() { + return filterNonStubExpression(mySetter); + } + + @NotNull + @Override + public Maybe getDeleter() { + return filterNonStubExpression(myDeleter); + } + public String getName() { return myName; } @@ -704,15 +722,29 @@ public class PyClassImpl extends PyPresentableElementImpl implement public Maybe getByDirection(@NotNull AccessDirection direction) { switch (direction) { case READ: - return myGetter; + return getGetter(); case WRITE: - return mySetter; + return getSetter(); case DELETE: - return myDeleter; + return getDeleter(); } throw new IllegalArgumentException("Unknown direction " + PyUtil.nvl(direction)); } + @Nullable + @Override + public PyType getType(@NotNull TypeEvalContext context) { + final Callable callable = myGetter.valueOrNull(); + if (callable != null) { + // Ignore return types of non stub-based elements if we are not allowed to use AST + if (!(callable instanceof StubBasedPsiElement) && !context.maySwitchToAST(callable)) { + return null; + } + return callable.getReturnType(context, null); + } + return null; + } + @NotNull @Override protected Maybe translate(@Nullable PyExpression expr) { @@ -733,6 +765,17 @@ public class PyClassImpl extends PyPresentableElementImpl implement return NONE; } + @NotNull + private static Maybe filterNonStubExpression(@NotNull Maybe maybeCallable) { + final Callable callable = maybeCallable.valueOrNull(); + if (callable != null) { + if (!(callable instanceof StubBasedPsiElement)) { + return UNKNOWN_CALL; + } + } + return maybeCallable; + } + public String toString() { return "property(" + myGetter + ", " + mySetter + ", " + myDeleter + ", " + myDoc + ")"; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index f77f6501f6f8..3e702bf69ef5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -27,7 +27,6 @@ import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.refactoring.PyDefUseUtil; -import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -269,10 +268,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere if (classType.isDefinition()) { return Ref.create(PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY)); } - final Maybe accessor = property.getByDirection(AccessDirection.of(this)); - final Callable callable = accessor.valueOrNull(); - final PyType type = (callable != null) ? callable.getReturnType(context, this) : null; - return Ref.create(type); + if (AccessDirection.of(this) == AccessDirection.READ) { + final PyType type = property.getType(context); + if (type != null) { + return Ref.create(type); + } + } + return Ref.create(); } } else if (qualifierType instanceof PyUnionType) { diff --git a/python/testData/property/Classic.py b/python/testData/property/Classic.py index c7293b63d2e4..c48b4503b513 100644 --- a/python/testData/property/Classic.py +++ b/python/testData/property/Classic.py @@ -19,6 +19,6 @@ class A(object): v1 = property(getter, setter) v2 = property(fset=setter, fdel=deleter, fget=getter, doc="doc of v2") - v3 = property(lambda self: self._v, None, (deleter)) + v3 = property(lambda self: 0, None, (deleter)) v4 = otherworldly # NOTE: not supported yet v5 = property(v5getter, v5setter) diff --git a/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java b/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java index 0ba9de3683eb..e72e10aaea5f 100644 --- a/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java +++ b/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java @@ -2,6 +2,8 @@ package com.jetbrains.python; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.toolbox.Maybe; public class PyClassicPropertyTest extends PyTestCase { @@ -78,6 +80,9 @@ public class PyClassicPropertyTest extends PyTestCase { accessor = p.getGetter(); assertFalse(accessor.isDefined()); + final PyType codeInsightType = p.getType(TypeEvalContext.codeInsightFallback()); + assertNull(codeInsightType); + accessor = p.getSetter(); assertTrue(accessor.isDefined()); assertNull(accessor.value());