diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index b70a4604d3f8..62e9f3735bc8 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -136,11 +136,11 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine /** * Non-recursively searches for a property for which the given function is a getter, setter or deleter. * - * @param function the function which may be an accessor + * @param callable the function which may be an accessor * @return the property, or null */ @Nullable - Property findPropertyByFunction(PyFunction function); + Property findPropertyByCallable(Callable callable); /** * @param parent diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 4643f69118dc..1c9e818fe85e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -662,14 +662,14 @@ public class PyClassImpl extends PyPresentableElementImpl implement } @Override - public Property findPropertyByFunction(PyFunction function) { + public Property findPropertyByCallable(Callable callable) { if (myPropertyCache == null) { myPropertyCache = initializePropertyCache(); } for (Property property : myPropertyCache.values()) { - if (property.getGetter().valueOrNull() == function || - property.getSetter().valueOrNull() == function || - property.getDeleter().valueOrNull() == function) { + if (property.getGetter().valueOrNull() == callable || + property.getSetter().valueOrNull() == callable || + property.getDeleter().valueOrNull() == callable) { return property; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 40cd988060cd..e15e43208c03 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -493,7 +493,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp public Property getProperty() { final PyClass containingClass = getContainingClass(); if (containingClass != null) { - return containingClass.findPropertyByFunction(this); + return containingClass.findPropertyByCallable(this); } return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index a626ffbf1fab..de36cbafed9b 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -8,10 +8,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.psi.stubs.StubUpdatingIndex; @@ -376,17 +373,6 @@ public class PyQualifiedReference extends PyReferenceImpl { if (isLocalScope(element)) { return false; } - PsiElement resolveResult = resolve(); - if (resolveResult instanceof PyImportedModule) { - resolveResult = resolveResult.getNavigationElement(); - } - if (element instanceof PsiDirectory && resolveResult instanceof PyFile && - PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) { - return true; - } - if (resolveResult == element) { - return true; - } final String referencedName = myElement.getReferencedName(); if (element instanceof PyFunction && Comparing.equal(referencedName, ((PyFunction)element).getName()) && ((PyFunction)element).getContainingClass() != null && !PyNames.INIT.equals(referencedName)) { @@ -399,6 +385,30 @@ public class PyQualifiedReference extends PyReferenceImpl { } } } + for (ResolveResult result : multiResolve(false)) { + if (result instanceof ImplicitResolveResult) { + continue; + } + PsiElement resolveResult = result.getElement(); + if (isResolvedToResult(element, resolveResult)) { + return true; + } + } + + return false; + } + + private boolean isResolvedToResult(PsiElement element, PsiElement resolveResult) { + if (resolveResult instanceof PyImportedModule) { + resolveResult = resolveResult.getNavigationElement(); + } + if (element instanceof PsiDirectory && resolveResult instanceof PyFile && + PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) { + return true; + } + if (resolveResult == element) { + return true; + } if (resolveResult instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)resolveResult) && element instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)element) && Comparing.equal( ((PyTargetExpression)resolveResult).getReferencedName(), @@ -415,7 +425,6 @@ public class PyQualifiedReference extends PyReferenceImpl { if (resolvesToWrapper(element, resolveResult)) { return true; } - return false; } diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java index 1ffe17db7623..692f5ce44650 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java @@ -6,10 +6,7 @@ import com.intellij.psi.PsiElement; import com.intellij.util.Processor; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; -import com.jetbrains.python.psi.Callable; -import com.jetbrains.python.psi.Property; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.psi.search.PySuperMethodsSearch; import com.jetbrains.python.toolbox.Maybe; @@ -78,6 +75,14 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor { } return null; } + final Property property = containingClass.findPropertyByCallable(function); + final PyTargetExpression site; + if (property != null) { + site = property.getDefinitionSite(); + if (site != null) { + return site; + } + } return function; } @@ -93,7 +98,7 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor { }); final PyClass containingClass = function.getContainingClass(); if (containingClass != null) { - final Property property = containingClass.findPropertyByFunction(function); + final Property property = containingClass.findPropertyByCallable(function); if (property != null) { addRename(allRenames, newName, property.getGetter()); addRename(allRenames, newName, property.getSetter()); diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java index fb377fb8ca19..c1bd1af6ec20 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java @@ -1,10 +1,13 @@ package com.jetbrains.python.refactoring.rename; +import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author yole @@ -35,4 +38,25 @@ public class RenamePyVariableProcessor extends RenamePyElementProcessor { public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) { PyCodeInsightSettings.getInstance().RENAME_SEARCH_NON_CODE_FOR_VARIABLE = enabled; } + + @Nullable + @Override + public PsiElement substituteElementToRename(PsiElement element, @Nullable Editor editor) { + if (element instanceof PyLambdaExpression) { + final PyLambdaExpression lambdaExpression = (PyLambdaExpression)element; + final ScopeOwner owner = ScopeUtil.getScopeOwner(lambdaExpression); + if (owner instanceof PyClass) { + final PyClass cls = (PyClass)owner; + final Property property = cls.findPropertyByCallable(lambdaExpression); + if (property != null) { + final PyTargetExpression site = property.getDefinitionSite(); + if (site != null) { + return site; + } + } + } + return null; + } + return element; + } } diff --git a/python/testData/refactoring/rename/renameOldStyleProperty.py b/python/testData/refactoring/rename/renameOldStyleProperty.py new file mode 100644 index 000000000000..57b9326c7e07 --- /dev/null +++ b/python/testData/refactoring/rename/renameOldStyleProperty.py @@ -0,0 +1,18 @@ +class C(object): + def __init__(self): + self._foo = 'foo' + + def get_foo(self): + return self._foo + + def set_foo(self, value): + self._foo = value + + def __str__(self): + return self.foo + + foo = property(get_foo, set_foo) + + +c = C() +print(c.foo) diff --git a/python/testData/refactoring/rename/renameOldStyleProperty_after.py b/python/testData/refactoring/rename/renameOldStyleProperty_after.py new file mode 100644 index 000000000000..e477cf88024f --- /dev/null +++ b/python/testData/refactoring/rename/renameOldStyleProperty_after.py @@ -0,0 +1,18 @@ +class C(object): + def __init__(self): + self._foo = 'foo' + + def get_foo(self): + return self._foo + + def set_foo(self, value): + self._foo = value + + def __str__(self): + return self.bar + + bar = property(get_foo, set_foo) + + +c = C() +print(c.bar) diff --git a/python/testData/refactoring/rename/renamePropertyWithLambda.py b/python/testData/refactoring/rename/renamePropertyWithLambda.py new file mode 100644 index 000000000000..504045ba1e87 --- /dev/null +++ b/python/testData/refactoring/rename/renamePropertyWithLambda.py @@ -0,0 +1,5 @@ +class C(object): + foo = property(lambda self: 'bar') + +c = C() +print(c.foo, c.foo) diff --git a/python/testData/refactoring/rename/renamePropertyWithLambda_after.py b/python/testData/refactoring/rename/renamePropertyWithLambda_after.py new file mode 100644 index 000000000000..f3e69d292d93 --- /dev/null +++ b/python/testData/refactoring/rename/renamePropertyWithLambda_after.py @@ -0,0 +1,5 @@ +class C(object): + bar = property(lambda self: 'bar') + +c = C() +print(c.bar, c.bar) diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java index da1c07175ce3..efa699c65442 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java @@ -137,6 +137,16 @@ public class PyRenameTest extends PyTestCase { doRenameConflictTest("foo", "A variable named 'foo' is already defined in variableNameConflict.py"); } + // PY-8315 + public void testRenamePropertyWithLambda() { + doTest("bar"); + } + + // PY-8315 + public void testRenameOldStyleProperty() { + doTest("bar"); + } + private void doRenameConflictTest(String newName, String expectedConflict) { myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); try {