PY-6637 Don't show refactoring for all sorts of special methods
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Fri, 2 Oct 2015 16:06:53 +0000 (19:06 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Mon, 5 Oct 2015 10:09:04 +0000 (13:09 +0300)
python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java
python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeFunctionTopLevelRefactoring.java
python/testData/refactoring/convertTopLevel/refactoringAvailability.py
python/testSrc/com/jetbrains/python/refactoring/PyMakeFunctionTopLevelTest.java

index 6a3fc84073f57da164a7f8a4a9f3198091bdfd51..8dd70935683b36754e123d4930b67442d5227466 100644 (file)
@@ -31,14 +31,16 @@ import java.util.Set;
  * @author yole
  */
 public class PySuperMethodsSearchExecutor implements QueryExecutor<PsiElement, PySuperMethodsSearch.SearchParameters> {
+  @Override
   public boolean execute(@NotNull final PySuperMethodsSearch.SearchParameters queryParameters,
                          @NotNull final Processor<PsiElement> consumer) {
-    PyFunction func = queryParameters.getDerivedMethod();
-    String name = func.getName();
-    PyClass containingClass = func.getContainingClass();
-    Set<PyClass> foundMethodContainingClasses = new HashSet<PyClass>();
+    final PyFunction func = queryParameters.getDerivedMethod();
+    final String name = func.getName();
+    final PyClass containingClass = func.getContainingClass();
+    final Set<PyClass> foundMethodContainingClasses = new HashSet<PyClass>();
+    final TypeEvalContext context = queryParameters.getContext();
     if (name != null && containingClass != null) {
-      for (PyClass superClass : containingClass.getAncestorClasses(null)) {
+      for (PyClass superClass : containingClass.getAncestorClasses(context)) {
         if (!queryParameters.isDeepSearch()) {
           boolean isAlreadyFound = false;
           for (PyClass alreadyFound : foundMethodContainingClasses) {
@@ -62,14 +64,13 @@ public class PySuperMethodsSearchExecutor implements QueryExecutor<PsiElement, P
         }
 
 
-        final TypeEvalContext context = queryParameters.getContext();
         if (superMethod == null && context != null) {
           // If super method still not found and we have context, we may use it to find method
           final PyClassLikeType classLikeType = PyUtil.as(context.getType(superClass), PyClassLikeType.class);
           if (classLikeType != null) {
-            for (final PyFunction function : PyClassLikeTypeUtil.getMembersOfType(classLikeType, PyFunction.class, context)) {
+            for (PyFunction function : PyClassLikeTypeUtil.getMembersOfType(classLikeType, PyFunction.class, context)) {
               final String elemName = function.getName();
-              if (elemName != null && elemName.equals(queryParameters.getDerivedMethod().getName())) {
+              if (elemName != null && elemName.equals(func.getName())) {
                 consumer.process(function);
               }
             }
index c40c9477afd02d64bad6d72674f60cad5395ea5e..62d19df7980857d9650042a61b7aa25e339190d0 100644 (file)
@@ -31,6 +31,9 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
 import com.jetbrains.python.psi.PyFunction;
 import com.jetbrains.python.psi.PyReferenceExpression;
 import com.jetbrains.python.psi.PyUtil;
+import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
+import com.jetbrains.python.psi.search.PySuperMethodsSearch;
+import com.jetbrains.python.psi.types.TypeEvalContext;
 import com.jetbrains.python.refactoring.PyBaseRefactoringAction;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -63,7 +66,7 @@ public class PyMakeFunctionTopLevelRefactoring extends PyBaseRefactoringAction {
 
   @Nullable
   private static PyFunction findTargetFunction(@NotNull PsiElement element) {
-    if (isLocalFunction(element) || isInstanceMethod(element)) {
+    if (isLocalFunction(element) || isSuitableInstanceMethod(element)) {
       return (PyFunction)element;
     }
     final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
@@ -71,19 +74,27 @@ public class PyMakeFunctionTopLevelRefactoring extends PyBaseRefactoringAction {
       return null;
     }
     final PsiElement resolved = refExpr.getReference().resolve();
-    if (isLocalFunction(resolved) || isInstanceMethod(resolved)) {
+    if (isLocalFunction(resolved) || isSuitableInstanceMethod(resolved)) {
       return (PyFunction)resolved;
     }
     return null;
   }
 
-  private static boolean isInstanceMethod(@Nullable PsiElement element) {
+  private static boolean isSuitableInstanceMethod(@Nullable PsiElement element) {
     final PyFunction function = as(element, PyFunction.class);
-    if (function == null) {
+    if (function == null || function.getContainingClass() == null) {
       return false;
     }
-    final PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(function);
-    return flags != null && flags.isInstanceMethod();
+    final String funcName = function.getName();
+    if (funcName == null || PyUtil.isSpecialName(funcName)) {
+      return false;
+    }
+    final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(function.getProject(), function.getContainingFile());
+    if (PySuperMethodsSearch.search(function, typeEvalContext).findFirst() != null) return false;
+    if (PyOverridingMethodsSearch.search(function, true).findFirst() != null) return false;
+    if (function.getDecoratorList() != null || function.getModifier() != null) return false;
+    if (function.getContainingClass().findPropertyByCallable(function) != null) return false;
+    return true;
   }
 
   private static boolean isLocalFunction(@Nullable PsiElement resolved) {
@@ -101,7 +112,7 @@ public class PyMakeFunctionTopLevelRefactoring extends PyBaseRefactoringAction {
           final PyFunction function = findTargetFunction(element);
           if (function != null) {
             final PyBaseMakeFunctionTopLevelProcessor processor;
-            if (isInstanceMethod(function)) {
+            if (isSuitableInstanceMethod(function)) {
               processor = new PyMakeMethodTopLevelProcessor(function, editor);
             }
             else {
index 12c8db308f012443beb3a213b8920f23e895bf16..43c170d99fbe138a7eadb1544b6a3aff0a8208a3 100644 (file)
@@ -1,16 +1,33 @@
-def fu<caret>nc():
+def func():
     def local():
         pass
-    
+
 
 class C:
     def method(self):
         pass
-    
+
     @staticmethod
     def static_method(x):
         pass
-    
+
     @classmethod
     def class_method(self):
-        pass
\ No newline at end of file
+        pass
+
+    @property
+    def field(self):
+        return self._x
+
+    def __magic__(self):
+        pass
+
+
+class Base:
+    def overridden_method(self):
+        pass
+
+
+class Subclass(Base):
+    def overridden_method(self):
+        super(Subclass, self).overridden_method()
index 7f18e4809cf42001722826899e0e9479f30163a4..947c51600d4b26a774a83897cb346595687b5945 100644 (file)
@@ -96,6 +96,15 @@ public class PyMakeFunctionTopLevelTest extends PyTestCase {
     assertFalse(isActionEnabled());
     moveByText("class_method");
     assertFalse(isActionEnabled());
+    
+    // Super method
+    moveByText("overridden_method");
+    assertFalse(isActionEnabled());
+    
+    moveByText("property");
+    assertFalse(isActionEnabled());
+    moveByText("__magic__");
+    assertFalse(isActionEnabled());
   }
 
   // PY-6637