PY-31299 Fix resolution of built-in classes' names in PyPsiFacade

Additionally, I extracted tests on PyPsiFacade implementation into a dedicated
test case.
This commit is contained in:
Mikhail Golubev
2018-09-26 19:51:17 +03:00
parent 1014dcc0eb
commit cd53226019
9 changed files with 47 additions and 12 deletions
@@ -64,6 +64,10 @@ public abstract class PyPsiFacade {
/**
* 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.
* <p>
* The only exception to the rule above are built-in classes as it's too cumbersome to explicitly specify "__builtin__" or "builtins"
* prefix for them each time, and, overall, it's rather intuitive that these classes can be found solely by their short names.
* <p>
* The anchor element is needed only to detect the corresponding module and its SDK.
*
* @param qName qualified name of the required class
@@ -91,12 +91,18 @@ public class PyPsiFacadeImpl extends PyPsiFacade {
@Nullable
@Override
public final PyClass createClassByQName(@NotNull final String qName, @NotNull final PsiElement anchor) {
final QualifiedName qualifiedName = QualifiedName.fromDottedString(qName);
// Only built-in classes can be found by their unqualified names.
if (qualifiedName.getComponentCount() == 1) {
return PyBuiltinCache.getInstance(anchor).getClass(qName);
}
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))
return StreamEx.of(resolveQualifiedName(qualifiedName, resolveContext))
.select(PyClass.class)
.findFirst()
.orElse(null);
@@ -496,8 +496,8 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
final List<PsiElement> elements = PyUtil.multiResolveTopPriority((PsiPolyVariantReference)ref);
assertEquals(2, elements.size());
final Set<String> parentNames = elements.stream()
.filter(e -> e instanceof PyFile)
.map(e -> ((PyFile)e).getVirtualFile().getParent().getName()).collect(Collectors.toSet());
.filter(e -> e instanceof PyFile)
.map(e -> ((PyFile)e).getVirtualFile().getParent().getName()).collect(Collectors.toSet());
assertContainsElements(parentNames, "root", "ext");
});
}
@@ -588,13 +588,4 @@ 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()));
});
}
}
@@ -0,0 +1,34 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyPsiFacade;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import java.util.Collections;
public class PyPsiFacadeTest extends PyTestCase {
public void testCreateClassByQNameDoesntDependOnExistingImports() {
myFixture.copyDirectoryToProject(getTestName(true), "");
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()));
});
}
public void testCreateClassByQNameCanResolveUnqualifiedNamesOfBuiltinClasses() {
final PsiFile file = myFixture.configureByText("a.py", "");
final PyPsiFacade facade = PyPsiFacade.getInstance(myFixture.getProject());
final PyClass builtinInt = facade.createClassByQName("int", file);
assertNotNull(builtinInt);
assertTrue(PyBuiltinCache.getInstance(file).isBuiltin(builtinInt));
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/psiFacade/";
}
}