PY-19989 Don't store results of implicit relative import in PythonPathCache

This commit is contained in:
Mikhail Golubev
2016-07-07 11:54:46 +03:00
parent 49367632f5
commit 4c7269a765
7 changed files with 68 additions and 6 deletions
@@ -19,7 +19,6 @@ import com.google.common.base.Preconditions;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
@@ -604,6 +603,14 @@ public class PyPsiUtils {
Preconditions.checkArgument(!module.isDisposed(), String.format("Module %s is disposed", module));
}
@NotNull
public static PsiFileSystemItem getFileSystemItem(@NotNull PsiElement element) {
if (element instanceof PsiFileSystemItem) {
return (PsiFileSystemItem)element;
}
return element.getContainingFile();
}
private static abstract class TopLevelVisitor extends PyRecursiveElementVisitor {
public void visitPyElement(final PyElement node) {
super.visitPyElement(node);
@@ -28,15 +28,13 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.console.PydevConsoleRunner;
import com.jetbrains.python.facet.PythonPathContributingFacet;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
@@ -44,6 +42,7 @@ import com.jetbrains.python.psi.impl.PyImportResolver;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -52,6 +51,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* Resolves the specified qualified name in the specified context (module, all modules or a file) to a file or directory.
*
@@ -235,6 +236,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
final PsiFile footholdFile = myContext.getFootholdFile();
checkValidForTests(footholdFile);
boolean foundRelativeImport = false;
if (myRelativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
PsiDirectory dir = footholdFile.getContainingDirectory();
checkValidForTests(dir);
@@ -246,12 +248,13 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
PsiElement module = resolveModuleAt(dir);
checkValidForTests(module);
if (module != null) {
foundRelativeImport = isRelativeImportResult(dir, module);
addRoot(module, true);
}
}
final PythonPathCache cache = findMyCache();
final boolean mayCache = cache != null && !myWithoutRoots && !myWithoutForeign;
final boolean mayCache = cache != null && !myWithoutRoots && !myWithoutForeign && !foundRelativeImport;
if (mayCache) {
final List<PsiElement> cachedResults = cache.get(myQualifiedName);
if (cachedResults != null) {
@@ -293,6 +296,22 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
return results;
}
private boolean isRelativeImportResult(@NotNull PsiDirectory rootDirectory, @NotNull PsiElement result) {
if (myRelativeLevel > 0) {
return true;
}
final boolean isPython2 = LanguageLevel.forElement(rootDirectory).isOlderThan(LanguageLevel.PYTHON30);
if (myRelativeLevel == 0 && isPython2 && PyUtil.isPackage(rootDirectory, false, null)) {
// Candidate for implicit relative import doesn't necessarily means that the same module cannot be imported absolutely
final PsiFileSystemItem moduleOrPackage = as(result, PsiFileSystemItem.class);
if (moduleOrPackage != null && !myQualifiedName.equals(QualifiedNameFinder.findShortestImportableQName(moduleOrPackage))) {
return true;
}
}
return false;
}
/**
* Resolve relative imports from sdk root to the skeleton dir
*/
@@ -421,6 +440,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
*
* @param directory where to start from; top qualifier will be searched for here.
*/
@Contract("null -> null")
@Nullable
public PsiElement resolveModuleAt(@Nullable PsiDirectory directory) {
// prerequisites
@@ -0,0 +1,2 @@
import mod
<ref>
@@ -0,0 +1,2 @@
import mod
<ref>
@@ -18,6 +18,8 @@ package com.jetbrains.python;
import com.google.common.collect.Lists;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFileImpl;
@@ -26,10 +28,14 @@ import com.jetbrains.python.fixtures.PyMultiFileResolveTestCase;
import com.jetbrains.python.fixtures.PyResolveTestCase;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author yole
@@ -459,4 +465,29 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
}
}
}
// PY-19989
public void testAmbiguousImplicitRelativeImport() {
prepareTestDirectory();
assertSameElements(doMultiResolveAndGetFileUrls("pkg2/__init__.py"), "pkg2/mod.py");
assertSameElements(doMultiResolveAndGetFileUrls("pkg/__init__.py"), "pkg/mod.py");
}
@NotNull
private List<String> doMultiResolveAndGetFileUrls(@NotNull String currentFilePath) {
myFixture.configureByFile(currentFilePath);
final PsiReference reference = PyResolveTestCase.findReferenceByMarker(myFixture.getFile());
final VirtualFile root = ModuleRootManager.getInstance(myFixture.getModule()).getSourceRoots()[0];
final Stream<PsiFileSystemItem> fileSystemItems;
if (reference instanceof PsiPolyVariantReference) {
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
fileSystemItems = Arrays.stream(results).map(r -> PyPsiUtils.getFileSystemItem(r.getElement()));
}
else {
fileSystemItems = Stream.of(PyPsiUtils.getFileSystemItem(reference.resolve()));
}
return fileSystemItems.map(f -> VfsUtilCore.getRelativeLocation(f.getVirtualFile(), root)).collect(Collectors.toList());
}
}