PY-46099 PY-47633 Don't remember usages in non-physical copies of methods in Pull Up/Down

PSI element copies exists in special non-physical files (DummyHolders),
and usually references in them cannot be resolved. It led both to exceptions,
when the rest of the code insight didn't expect resolve to happen in files other
than PyFile (PY-46099), and situations when we failed to resolve a usage in
a copy and restore the corresponding import (PY-47633).

Switching to processing original declarations also revealed a problem with
inserting imports -- we might have tried to insert impossible imports for
non-top-level symbols, such as class attributes and methods. Now, these are
ignored.

GitOrigin-RevId: 6816078596a2c0aced7045a80828b7e83ebee8c0
This commit is contained in:
Mikhail Golubev
2021-03-11 16:28:36 +03:00
committed by intellij-monorepo-bot
parent f9f34cc7f9
commit f76600603e
8 changed files with 55 additions and 26 deletions

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.QualifiedName;
@@ -233,10 +234,14 @@ public class PyPsiRefactoringUtil {
containingQName = qname.removeLastComponent();
importedName = qname.getLastComponent();
}
else {
// See PyClassRefactoringUtil.DynamicNamedElement
else if (PyUtil.isTopLevel(element) || element instanceof LightElement) {
containingQName = qname;
importedName = getOriginalName(element);
}
else {
return false;
}
final AddImportHelper.ImportPriority priority = AddImportHelper.getImportPriority(anchor, elementSource);
if (preferFromImport && !containingQName.getComponents().isEmpty() || !importingModuleOrPackage) {
return AddImportHelper.addOrUpdateFromImportStatement(file, containingQName.toString(), importedName, asName, priority, anchor);

View File

@@ -105,18 +105,6 @@ public final class PyClassRefactoringUtil {
return currentValue.getText();
}
@NotNull
public static List<PyFunction> copyMethods(Collection<? extends PyFunction> methods, PyClass superClass, boolean skipIfExist ) {
if (methods.isEmpty()) {
return Collections.emptyList();
}
for (final PsiElement e : methods) {
rememberNamedReferences(e);
}
final PyFunction[] elements = methods.toArray(PyFunction.EMPTY_ARRAY);
return addMethods(superClass, skipIfExist, elements);
}
/**
* Adds methods to class.
*

View File

@@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
@@ -127,20 +128,22 @@ class MethodsManager extends MembersManager<PyFunction> {
* @param skipIfExist skip (do not add) if method already exists
* @return newly added methods
*/
static List<PyElement> moveMethods(final PyClass from, final Collection<? extends PyFunction> methodsToMove, final boolean skipIfExist, final PyClass... to) {
final List<PyElement> result = new ArrayList<>();
for (final PyClass destClass : to) {
//We move copies here because there may be several destinations
final List<PyFunction> copies = new ArrayList<>(methodsToMove.size());
for (final PyFunction element : methodsToMove) {
final PyFunction newMethod = (PyFunction)element.copy();
copies.add(newMethod);
}
static List<PyElement> moveMethods(@NotNull PyClass from,
@NotNull Collection<? extends PyFunction> methodsToMove,
boolean skipIfExist,
PyClass @NotNull... to) {
if (methodsToMove.isEmpty() || to.length == 0) return Collections.emptyList();
result.addAll(PyClassRefactoringUtil.copyMethods(copies, destClass, skipIfExist));
final List<PyElement> result = new ArrayList<>();
for (PyFunction method : methodsToMove) {
PyClassRefactoringUtil.rememberNamedReferences(method);
}
for (PyClass destClass : to) {
//We move copies here because there may be several destinations
PyFunction[] copies = ContainerUtil.map2Array(methodsToMove, PyFunction.EMPTY_ARRAY, m -> (PyFunction)m.copy());
result.addAll(PyClassRefactoringUtil.addMethods(destClass, skipIfExist, copies));
}
deleteElements(methodsToMove);
return result;
}

View File

@@ -0,0 +1,3 @@
class MyClass:
def method(self):
pass

View File

@@ -1,3 +1,5 @@
import logging
from SuperClass import SuperClass
@@ -14,7 +16,7 @@ class AnyClass(SuperClass):
@new_property.setter
def new_property(self, value):
pass
logging.debug("Setting %s", value)
@new_property.deleter
def new_property(self):

View File

@@ -1,3 +1,6 @@
import logging
class SuperClass(object):
def __init__(self):
pass
@@ -8,7 +11,7 @@ class SuperClass(object):
@new_property.setter
def new_property(self, value):
pass
logging.debug("Setting %s", value)
@new_property.deleter
def new_property(self):

View File

@@ -16,15 +16,23 @@
package com.jetbrains.python;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiReference;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority;
import com.jetbrains.python.fixtures.PyResolveTestCase;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.PyPsiRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import static com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority.*;
/**
@@ -191,6 +199,23 @@ public class PyAddImportTest extends PyTestCase {
doTestRelativeImport("foo", "lib", "foo/bar/test");
}
public void testImportForMethodCannotBeAdded() {
String testName = getTestName(true);
myFixture.copyDirectoryToProject(testName, "");
myFixture.configureByFile("main.py");
Collection<PyClass> pyClasses = PyClassNameIndex.find("MyClass", myFixture.getProject(), false);
PyClass pyClass = assertOneElement(pyClasses);
TypeEvalContext typeEvalContext = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
PyFunction method = pyClass.findMethodByName("method", false, typeEvalContext);
assertNotNull(method);
Ref<Boolean> inserted = Ref.create();
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), () -> {
inserted.set(PyPsiRefactoringUtil.insertImport(myFixture.getFile(), method, null));
});
assertFalse(inserted.get());
myFixture.checkResultByFile(testName + "/main.py");
}
private void doAddOrUpdateFromImport(final String path, final String name, final ImportPriority priority) {
myFixture.configureByFile(getTestName(true) + ".py");
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), () -> {