PY-31299 Migrate PyPsiFacade#createClassByQName implementation to PyResolveImportUtil

Using PyTypeParser for this purpose is, first, too heavyweight since it does a lot of
unnecessary work, and, second, introduces unexpected problems related to the fact
that it was originally intended to resolve types in docstrings and thus too permissive
and depends on surrounding context such as existing imports.
This commit is contained in:
Mikhail Golubev
2018-09-26 18:15:23 +03:00
parent 98c96a2953
commit 2d2976a889
8 changed files with 38 additions and 4 deletions
@@ -61,6 +61,14 @@ public abstract class PyPsiFacade {
@Nullable
public abstract PyType parseTypeAnnotation(@NotNull String annotation, @NotNull PsiElement anchor);
/**
* Retrieve a top-level class by its qualified name. The name provided is supposed to be <em>fully qualified absolute name</em>
* of the class, neither relative to the containing file of the anchor element, nor dependent on its imports.
* The anchor element is needed only to detect the corresponding module and its SDK.
*
* @param qName qualified name of the required class
* @param anchor arbitrary element located in the same module/SDK as the required class
*/
@Nullable
public abstract PyClass createClassByQName(@NotNull String qName, @NotNull PsiElement anchor);
@@ -15,16 +15,21 @@
*/
package com.jetbrains.python.psi.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ObjectUtils;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyPsiFacade;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.psi.types.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -86,8 +91,15 @@ public class PyPsiFacadeImpl extends PyPsiFacade {
@Nullable
@Override
public final PyClass createClassByQName(@NotNull final String qName, @NotNull final PsiElement anchor) {
final PyClassType classType = PyUtil.as(parseTypeAnnotation(qName, anchor), PyClassType.class);
return (classType != null ? classType.getPyClass() : null);
final Module module = ModuleUtilCore.findModuleForPsiElement(ObjectUtils.notNull(anchor.getContainingFile(), anchor));
if (module == null) return null;
// Don't use PyResolveImportUtil.fromFoothold here as setting foothold file is going to affect resolve results
// particularly if the anchor element happens to be in the same file as the target class.
final PyQualifiedNameResolveContext resolveContext = PyResolveImportUtil.fromModule(module).copyWithMembers();
return StreamEx.of(resolveQualifiedName(QualifiedName.fromDottedString(qName), resolveContext))
.select(PyClass.class)
.findFirst()
.orElse(null);
}
@Nullable
@@ -0,0 +1,2 @@
class MyClass:
pass
@@ -588,4 +588,13 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
public void testIncompleteFromImport() {
assertUnresolved();
}
public void testCreateClassByQNameDoesntDependOnExistingImports() {
prepareTestDirectory();
runWithSourceRoots(Collections.singletonList(myFixture.findFileInTempDir("src")), () -> {
myFixture.configureByFile("src/" + getTestName(false) + ".py");
final PyPsiFacade facade = PyPsiFacade.getInstance(myFixture.getProject());
assertNotNull(facade.createClassByQName("foo.bar.MyClass", myFixture.getFile()));
});
}
}