Move adding metaclass method (PY-30789)

This commit is contained in:
Semyon Proshev
2018-07-17 20:27:51 +03:00
parent e2855325d1
commit fff6955833
9 changed files with 50 additions and 62 deletions
@@ -41,6 +41,8 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -204,10 +206,10 @@ public final class PyClassRefactoringUtil {
/**
* Restores references saved by {@link #rememberNamedReferences(com.intellij.psi.PsiElement, String...)}.
* Restores references saved by {@link #rememberNamedReferences(PsiElement, String...)}.
*
* @param element newly created element to restore references
* @see #rememberNamedReferences(com.intellij.psi.PsiElement, String...)
* @see #rememberNamedReferences(PsiElement, String...)
*/
public static void restoreNamedReferences(@NotNull final PsiElement element) {
restoreNamedReferences(element, null);
@@ -337,9 +339,9 @@ public final class PyClassRefactoringUtil {
}
/**
* Searches for references inside some element (like {@link com.jetbrains.python.psi.PyAssignmentStatement}, {@link com.jetbrains.python.psi.PyFunction} etc
* Searches for references inside some element (like {@link PyAssignmentStatement}, {@link PyFunction} etc
* and stored them.
* After that you can add element to some new parent. Newly created element then should be processed via {@link #restoreNamedReferences(com.intellij.psi.PsiElement)}
* After that you can add element to some new parent. Newly created element then should be processed via {@link #restoreNamedReferences(PsiElement)}
* and all references would be restored.
*
* @param element element to store references for
@@ -526,10 +528,10 @@ public final class PyClassRefactoringUtil {
* @param paramExpressions param expressions. Like "object" or "MySuperClass". Will not add any param exp. if null.
* @param keywordArguments keyword args like "metaclass=ABCMeta". key-value pairs. Will not add any keyword arg. if null.
*/
public static void addSuperClassExpressions(@NotNull final Project project,
@NotNull final PyClass clazz,
@Nullable final Collection<String> paramExpressions,
@Nullable final Collection<Pair<String, String>> keywordArguments) {
private static void addSuperClassExpressions(@NotNull final Project project,
@NotNull final PyClass clazz,
@Nullable final Collection<String> paramExpressions,
@Nullable final Collection<Pair<String, String>> keywordArguments) {
final PyElementGenerator generator = PyElementGenerator.getInstance(project);
final LanguageLevel languageLevel = LanguageLevel.forElement(clazz);
@@ -597,11 +599,32 @@ public final class PyClassRefactoringUtil {
return PyUtil.addElementToStatementList(assignmentStatement, aClass.getStatementList(), true);
}
public static boolean addMetaClassIfNotExist(@NotNull PyClass cls, @NotNull PyClass metaClass, @NotNull TypeEvalContext context) {
final String metaClassName = metaClass.getName();
if (metaClassName == null) return false;
final PyType metaClassType = cls.getMetaClassType(false, context);
if (metaClassType != null) return false;
insertImport(cls, metaClass);
final LanguageLevel languageLevel = LanguageLevel.forElement(cls);
if (languageLevel.isPython2()) {
addClassAttributeIfNotExist(cls, PyNames.DUNDER_METACLASS, metaClassName);
}
else {
final List<Pair<String, String>> keywordArguments = Collections.singletonList(Pair.create(PyNames.METACLASS, metaClassName));
addSuperClassExpressions(cls.getProject(), cls, null, keywordArguments);
}
return true;
}
private static class DynamicNamedElement extends LightElement implements PsiNamedElement {
private final PsiFile myFile;
private final String myName;
public DynamicNamedElement(@NotNull PsiFile file, @NotNull String name) {
private DynamicNamedElement(@NotNull PsiFile file, @NotNull String name) {
super(file.getManager(), file.getLanguage());
myName = name;
myFile = file;
@@ -18,7 +18,7 @@ package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.FluentIterable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
@@ -29,7 +29,6 @@ import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
@@ -114,58 +113,26 @@ class MethodsManager extends MembersManager<PyFunction> {
// Add ABCMeta to new classes if needed
for (final PyClass aClass : classesToAddMetaAbc) {
if (addMetaAbcIfNeeded(aClass)) {
filesToCheckImport.add(aClass.getContainingFile());
final Project project = aClass.getProject();
final PsiFile file = aClass.getContainingFile();
final PyClass abcMetaClass = PyPsiFacade.getInstance(project).createClassByQName("abc." + PyNames.ABC_META_CLASS, aClass);
final TypeEvalContext context = TypeEvalContext.userInitiated(project, file);
if (abcMetaClass != null && PyClassRefactoringUtil.addMetaClassIfNotExist(aClass, abcMetaClass, context)) {
filesToCheckImport.add(file);
}
}
// Add imports for ABC if needed
for (final PsiFile file : filesToCheckImport) {
addImportFromAbc(file, PyNames.ABSTRACTMETHOD);
addImportFromAbc(file, PyNames.ABC_META_CLASS);
AddImportHelper.addOrUpdateFromImportStatement(file, ABC_META_PACKAGE, PyNames.ABSTRACTMETHOD, null, ImportPriority.BUILTIN, null);
PyClassRefactoringUtil.optimizeImports(file); //To remove redundant imports
}
}
/**
* Adds metaclass = ABCMeta for class if has no.
*
* @param aClass class where it should be added
* @return true if added. False if class already has metaclass so we did not touch it.
*/
// TODO: Copy/Paste with PyClass.getMeta..
private static boolean addMetaAbcIfNeeded(@NotNull final PyClass aClass) {
final PsiFile file = aClass.getContainingFile();
final PyType type = aClass.getMetaClassType(TypeEvalContext.userInitiated(aClass.getProject(), file));
if (type != null) {
return false; //User already has metaclass. He probably knows about metaclasses, so we should not add ABCMeta
}
final LanguageLevel languageLevel = LanguageLevel.forElement(aClass);
if (languageLevel.isPy3K()) { //TODO: Copy/paste, use strategy because we already has the same check in #couldBeAbstract
// Add (metaclass= for Py3K
PyClassRefactoringUtil
.addSuperClassExpressions(aClass.getProject(), aClass, null, Collections.singletonList(Pair.create(PyNames.METACLASS,
PyNames.ABC_META_CLASS)));
}
else {
// Add __metaclass__ for Py2
PyClassRefactoringUtil.addClassAttributeIfNotExist(aClass, PyNames.DUNDER_METACLASS, PyNames.ABC_META_CLASS);
}
return true;
}
/**
* Adds import from ABC module
*
* @param file where to add import
* @param nameToImport what to import
*/
private static void addImportFromAbc(@NotNull final PsiFile file, @NotNull final String nameToImport) {
AddImportHelper.addOrUpdateFromImportStatement(file, ABC_META_PACKAGE, nameToImport, null, ImportPriority.BUILTIN, null);
}
/**
* Moves methods (as opposite to {@link #makeMethodsAbstract(java.util.Collection, com.jetbrains.python.psi.PyClass...)})
* Moves methods (as opposite to {@link #makeMethodsAbstract(Collection, PyClass...)})
*
* @param from source
* @param methodsToMove what to move
@@ -211,8 +178,6 @@ class MethodsManager extends MembersManager<PyFunction> {
assert flags != null : "Function should be called on method!";
final boolean py3K = LanguageLevel.forElement(function).isPy3K();
//TODO: use strategy because we already has the same check in #addMetaAbcIfNeeded
return flags.isInstanceMethod() || py3K; //Any method could be made abstract in py3
}
@@ -1,4 +1,4 @@
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class A:
@@ -1,5 +1,5 @@
# coding=utf-8
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class A:
@@ -1,4 +1,4 @@
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class NewParent(object):
@@ -1,4 +1,4 @@
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class A:
@@ -1,5 +1,5 @@
# coding=utf-8
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class A:
@@ -1,4 +1,4 @@
from abc import abstractmethod, ABCMeta
from abc import ABCMeta, abstractmethod
class Parent(object):
@@ -1,4 +1,4 @@
from abc import object, abstractmethod, ABCMeta
from abc import object, ABCMeta, abstractmethod
class Parent(object, metaclass=ABCMeta):