Refactored API for resolving Python qualified names in roots

Rewritten it in Kotlin and transformed mutable build-style API to a
more functional immutable version. It should become easier to extend
the PyQualifiedNameResolveContext and the
PyResolveImportUtil.resolveQualfiedName() in order to add resolve to
Python stub files by default.
This commit is contained in:
Andrey Vlasovskikh
2017-02-02 01:22:31 +03:00
committed by Andrey Vlasovskikh
parent 7a1b4d789c
commit f0d6fdce64
19 changed files with 514 additions and 764 deletions
@@ -5,12 +5,12 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.impl.PyImportResolver;
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import org.jetbrains.annotations.Nullable;
public class PyStudyImportResolver implements PyImportResolver {
@Nullable
public PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots) {
public PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) {
if (StudyTaskManager.getInstance(context.getProject()).getCourse() == null) {
return null;
}
@@ -21,7 +21,7 @@ import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import org.jetbrains.annotations.Nullable;
/**
@@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable;
*/
public class PyJavaImportResolver implements PyImportResolver {
@Nullable
public PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots) {
public PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) {
String fqn = name.toString();
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(context.getProject());
final PsiPackage aPackage = psiFacade.findPackage(fqn);
@@ -16,8 +16,8 @@
package com.jetbrains.python.codeInsight;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.*;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,9 +39,9 @@ public abstract class PyPsiPath {
@Nullable
@Override
public PsiElement resolve(PsiElement context) {
PyPsiFacade pyPsiFacade = PyPsiFacade.getInstance(context.getProject());
QualifiedNameResolver visitor = pyPsiFacade.qualifiedNameResolver(myQualifiedName).fromElement(context);
return visitor.firstResult();
final PyPsiFacade facade = PyPsiFacade.getInstance(context.getProject());
return facade.resolveQualifiedName(myQualifiedName, facade.createResolveContextFromFoothold(context))
.stream().findFirst().orElse(null);
}
}
@@ -20,7 +20,7 @@ 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.jetbrains.python.psi.resolve.QualifiedNameResolver;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
@@ -38,9 +38,10 @@ public abstract class PyPsiFacade {
}
@NotNull
public abstract QualifiedNameResolver qualifiedNameResolver(String qNameString);
public abstract QualifiedNameResolver qualifiedNameResolver(QualifiedName qualifiedName);
public abstract List<PsiElement> resolveQualifiedName(@NotNull QualifiedName name, @NotNull PyQualifiedNameResolveContext context);
@NotNull
public abstract PyQualifiedNameResolveContext createResolveContextFromFoothold(@NotNull PsiElement foothold);
/**
* @deprecated use {@link #createClassByQName(String, PsiElement)} or skeleton may be found
*/
@@ -18,7 +18,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import org.jetbrains.annotations.Nullable;
/**
@@ -28,5 +28,5 @@ public interface PyImportResolver {
ExtensionPointName<PyImportResolver> EP_NAME = ExtensionPointName.create("Pythonid.importResolver");
@Nullable
PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots);
PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots);
}
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Context for resolving Python qualified names with various options.
*
* @author vlan
*/
public interface PyQualifiedNameResolveContext {
@Nullable
PsiElement getFoothold();
int getRelativeLevel();
@Nullable
Sdk getSdk();
@Nullable
Module getModule();
@NotNull
Project getProject();
boolean getWithoutRoots();
boolean getWithoutForeign();
@NotNull
PsiManager getPsiManager();
boolean getWithMembers();
boolean getWithPlainDirectories();
boolean getVisitAllModules();
@Nullable
Sdk getEffectiveSdk();
boolean isValid();
@Nullable
PsiFile getFootholdFile();
@Nullable
PsiDirectory getContainingDirectory();
@NotNull
PyQualifiedNameResolveContext copyWithoutForeign();
@NotNull
PyQualifiedNameResolveContext copyWithMembers();
@NotNull
PyQualifiedNameResolveContext copyWithPlainDirectories();
@NotNull
PyQualifiedNameResolveContext copyWithRelative(int relativeLevel);
@NotNull
PyQualifiedNameResolveContext copyWithoutRoots();
}
@@ -1,101 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.PyNames;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class QualifiedNameResolveContext {
@Nullable private Module myModule;
private PsiFile myFootholdFile;
@NotNull PsiManager myPsiManager;
private Sdk mySdk;
public void copyFrom(QualifiedNameResolveContext context) {
myModule = context.getModule();
myPsiManager = context.getPsiManager();
mySdk = context.getSdk();
myFootholdFile = context.getFootholdFile();
}
public void setFromElement(PsiElement foothold) {
if (foothold instanceof PsiDirectory) {
myFootholdFile = ((PsiDirectory)foothold).findFile(PyNames.INIT_DOT_PY);
}
else {
myFootholdFile = foothold.getContainingFile().getOriginalFile();
}
myPsiManager = foothold.getManager();
myModule = ModuleUtilCore.findModuleForPsiElement(foothold);
}
public void setFromModule(Module module) {
myModule = module;
myPsiManager = PsiManager.getInstance(module.getProject());
}
public void setFromSdk(Project project, Sdk sdk) {
myPsiManager = PsiManager.getInstance(project);
mySdk = sdk;
}
public void setSdk(Sdk sdk) {
mySdk = sdk;
}
@Nullable
public Module getModule() {
return myModule;
}
public boolean isValid() {
if (myFootholdFile != null) {
return myFootholdFile.isValid();
}
return true;
}
@Nullable
public PsiFile getFootholdFile() {
return myFootholdFile;
}
@NotNull
public PsiManager getPsiManager() {
return myPsiManager;
}
@NotNull
public Project getProject() {
return myPsiManager.getProject();
}
public Sdk getSdk() {
return mySdk;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author yole
*/
public interface QualifiedNameResolver {
QualifiedNameResolver fromElement(@NotNull PsiElement foothold);
QualifiedNameResolver fromModule(@NotNull Module module);
QualifiedNameResolver fromSdk(@NotNull Project project, @NotNull Sdk sdk);
QualifiedNameResolver withAllModules();
QualifiedNameResolver withSdk(Sdk sdk);
QualifiedNameResolver withRelative(int relativeLevel);
QualifiedNameResolver withoutRoots();
QualifiedNameResolver withPlainDirectories();
@NotNull
List<PsiElement> resultsAsList();
@Nullable
PsiElement firstResult();
@NotNull
<T extends PsiElement> List<T> resultsOfType(Class<T> clazz);
@Nullable
<T extends PsiElement> T firstResultOfType(Class<T> clazz);
QualifiedNameResolver withContext(QualifiedNameResolveContext context);
QualifiedNameResolver withoutForeign();
Module getModule();
QualifiedNameResolver withMembers();
/**
* Resolves to some toplevel symbol like class or function.
* i.e.: "foo.package.MyClass".
* <strong>Module is required!</strong>
* Call {@link #fromModule(com.intellij.openapi.module.Module)} first!
* @param aClass expected class
* @param <T> expected class
* @return element if found
*/
@Nullable
<T extends PsiNamedElement> T resolveTopLevelMember(@NotNull Class<T> aClass);
}
@@ -35,6 +35,7 @@ import com.jetbrains.python.documentation.docstrings.SectionBasedDocString.Secti
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyExpressionCodeFragmentImpl;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.psi.types.PyNoneType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeProviderBase;
@@ -165,8 +166,8 @@ public class NumpyDocStringTypeProvider extends PyTypeProviderBase {
private static PyFunction resolveRedirectToFunction(@NotNull String redirect, @NotNull PsiElement reference) {
final QualifiedName qualifiedName = QualifiedName.fromDottedString(redirect);
final String functionName = qualifiedName.getLastComponent();
final PyPsiFacade facade = PyPsiFacade.getInstance(reference.getProject());
final List<PsiElement> items = facade.qualifiedNameResolver(qualifiedName.removeLastComponent()).fromElement(reference).resultsAsList();
final List<PsiElement> items = PyResolveImportUtil.resolveQualifiedName(qualifiedName.removeLastComponent(),
PyResolveImportUtil.fromFoothold(reference));
for (PsiElement item : items) {
if (item instanceof PsiDirectory) {
item = ((PsiDirectory)item).findFile(PyNames.INIT_DOT_PY);
@@ -19,9 +19,9 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.codeInsight.PyCustomMember;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyPsiFacade;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.psi.types.PyModuleMembersProvider;
import java.util.ArrayList;
@@ -68,10 +68,10 @@ public class NumpyModuleMembersProvider extends PyModuleMembersProvider {
}
private static void addTestingModule(PyFile module, List<PyCustomMember> members) {
PyPsiFacade psiFacade = PyPsiFacade.getInstance(module.getProject());
final QualifiedNameResolver resolver =
psiFacade.qualifiedNameResolver(QualifiedName.fromDottedString("numpy.testing")).withPlainDirectories().fromElement(module);
PsiElement testingModule = PyUtil.turnDirIntoInit(resolver.firstResult());
final PyQualifiedNameResolveContext context = PyResolveImportUtil.fromFoothold(module).copyWithPlainDirectories();
final PsiElement resolved = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString("numpy.testing"), context)
.stream().findFirst().orElse(null);
final PsiElement testingModule = PyUtil.turnDirIntoInit(resolved);
members.add(new PyCustomMember("testing", testingModule));
}
}
+11 -11
View File
@@ -26,9 +26,11 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -122,15 +124,13 @@ public abstract class QtFileType extends LanguageFileType implements INativeFile
@Nullable
private static String findToolInPackage(String toolName, Module module, Sdk sdk, String name) {
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(name).fromModule(module).withSdk(sdk);
List<PsiDirectory> elements = visitor.resultsOfType(PsiDirectory.class);
for (PsiDirectory directory : elements) {
VirtualFile tool = directory.getVirtualFile().findChild(toolName + ".exe");
if (tool != null) {
return tool.getPath();
}
}
return null;
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(name),
PyResolveImportUtil.fromSdk(module.getProject(), sdk));
return StreamEx.of(results).select(PsiDirectory.class)
.map(directory -> directory.getVirtualFile().findChild(toolName + ".exe"))
.filter(file -> file != null)
.map(file -> file.getPath())
.findFirst().orElse(null);
}
protected abstract String getToolName();
@@ -132,7 +132,8 @@ public class PyUserSkeletonsUtil {
final VirtualFile directory = getUserSkeletonsDirectory();
if (directory != null) {
final PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(directory);
PsiElement fileSkeleton = new QualifiedNameResolverImpl(qName).resolveModuleAt(psiDirectory);
PsiElement fileSkeleton = PyResolveImportUtil.resolveModuleAt(QualifiedName.fromDottedString(qName), psiDirectory,
PyResolveImportUtil.fromFoothold(foothold));
if (fileSkeleton instanceof PsiDirectory) {
fileSkeleton = PyUtil.getPackageElement((PsiDirectory)fileSkeleton, foothold);
}
@@ -22,9 +22,7 @@ import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyPsiFacade;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
@@ -43,15 +41,16 @@ public class PyPsiFacadeImpl extends PyPsiFacade {
myProject = project;
}
@Override
@NotNull
public QualifiedNameResolver qualifiedNameResolver(String qNameString) {
return new QualifiedNameResolverImpl(qNameString);
@Override
public List<PsiElement> resolveQualifiedName(@NotNull QualifiedName name, @NotNull PyQualifiedNameResolveContext context) {
return PyResolveImportUtil.resolveQualifiedName(name, context);
}
@NotNull
@Override
public QualifiedNameResolver qualifiedNameResolver(QualifiedName qualifiedName) {
return new QualifiedNameResolverImpl(qualifiedName);
public PyQualifiedNameResolveContext createResolveContextFromFoothold(@NotNull PsiElement foothold) {
return PyResolveImportUtil.fromFoothold(foothold);
}
@Nullable
@@ -38,6 +38,7 @@ import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -265,10 +266,9 @@ public class PyImportReference extends PyReferenceImpl {
}
private void fillFromQName(QualifiedName thisQName, InsertHandler<LookupElement> insertHandler) {
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(thisQName).fromElement(myCurrentFile);
for (PsiDirectory dir : visitor.resultsOfType(PsiDirectory.class)) {
fillFromDir(dir, insertHandler);
}
StreamEx.of(PyResolveImportUtil.resolveQualifiedName(thisQName, PyResolveImportUtil.fromFoothold(myCurrentFile)))
.select(PsiDirectory.class)
.forEach(directory -> fillFromDir(directory, insertHandler));
}
private void addImportedNames(@NotNull PyImportElement[] importElements) {
@@ -0,0 +1,82 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.jetbrains.python.PyNames
import com.jetbrains.python.console.PydevConsoleRunner
import com.jetbrains.python.psi.PyUtil
data class PyQualifiedNameResolveContextImpl(private val psiManager: PsiManager, private val module: Module?,
private val foothold: PsiElement?, private val sdk: Sdk?,
private val relativeLevel : Int = -1,
private val withoutRoots : Boolean = false,
private val withoutForeign: Boolean = false,
private val withMembers : Boolean = false,
private val withPlainDirectories : Boolean = false) : PyQualifiedNameResolveContext {
override fun getFoothold() = foothold
override fun getRelativeLevel() = relativeLevel
override fun getSdk() = sdk
override fun getModule() = module
override fun getProject() = psiManager.project
override fun getWithoutRoots() = withoutRoots
override fun getWithoutForeign() = withoutForeign
override fun getPsiManager() = psiManager
override fun getWithMembers() = withMembers
override fun getWithPlainDirectories() = withPlainDirectories
override fun getEffectiveSdk() = if (visitAllModules) PydevConsoleRunner.getConsoleSdk(foothold) else sdk
override fun isValid() = footholdFile?.isValid ?: true
override fun copyWithoutForeign() = copy(withoutForeign = true)
override fun copyWithMembers() = copy(withMembers = true)
override fun copyWithPlainDirectories() = copy(withPlainDirectories = true)
override fun copyWithRelative(relativeLevel : Int) = copy(relativeLevel = relativeLevel)
override fun copyWithoutRoots() = copy(withoutRoots = true)
override fun getContainingDirectory(): PsiDirectory? {
val file = footholdFile ?: return null
return if (relativeLevel > 0) ResolveImportUtil.stepBackFrom(file, relativeLevel) else file.containingDirectory
}
override fun getFootholdFile() = when (foothold) {
is PsiDirectory -> foothold.findFile(PyNames.INIT_DOT_PY)
else -> foothold?.containingFile?.originalFile
}
override fun getVisitAllModules(): Boolean {
val file = foothold
return file != null && (PydevConsoleRunner.isInPydevConsole(file) || PyUtil.isInScratchFile(file))
}
}
@@ -0,0 +1,302 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("PyResolveImportUtil")
package com.jetbrains.python.psi.resolve
import com.google.common.base.Preconditions
import com.intellij.facet.FacetManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.FileIndexFacade
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.psi.PsiManager
import com.intellij.psi.util.QualifiedName
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil
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
import com.jetbrains.python.psi.impl.PyImportResolver
import com.jetbrains.python.sdk.PySdkUtil
import com.jetbrains.python.sdk.PythonSdkType
/**
* Python resolve utilities for qualified names.
*
* @author vlan
*/
/**
* Resolves a qualified [name] a list of modules / top-level elements according to the [context].
*/
fun resolveQualifiedName(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
checkAccess()
if (!context.isValid) {
return emptyList()
}
val relativeDirectory = context.containingDirectory
val relativeResult = resolveWithRelativeLevel(name, context)
val foundRelativeImport = relativeDirectory != null && relativeResult != null &&
isRelativeImportResult(name, relativeDirectory, relativeResult, context)
val cache = findCache(context)
val mayCache = cache != null && !context.withoutRoots && !context.withoutForeign && !foundRelativeImport
if (mayCache) {
val cachedResults = cache?.get(name)
if (cachedResults != null) {
return listOf(listOfNotNull(relativeResult), cachedResults).flatten()
}
}
val allResults = listOf(listOfNotNull(relativeResult),
resultsFromRoots(name, context),
relativeResultsFromSkeletons(name, context),
foreignResults(name, context)).flatten()
val results = if (name.componentCount > 0) findFirstResults(allResults) else allResults
if (mayCache) {
cache?.put(name, results)
}
return results
}
/**
* Resolves a [name] to the first module member defined at the top-level.
*/
fun resolveTopLevelMember(name: QualifiedName, context : PyQualifiedNameResolveContext): PsiElement? {
checkAccess()
val memberName = name.lastComponent ?: return null
return resolveQualifiedName(name.removeLastComponent(), context)
.asSequence()
.filterIsInstance(PyFile::class.java)
.flatMap { it.multiResolveName(memberName).asSequence() }
.map { it.element }
.firstOrNull()
}
/**
* Resolves a [name] relative to the specified [directory].
*/
fun resolveModuleAt(name: QualifiedName, directory: PsiDirectory?, context: PyQualifiedNameResolveContext): PsiElement? {
checkAccess()
if (directory == null || !directory.isValid) {
return null
}
return name.components.fold(directory as PsiElement?) { seeker, component ->
if (component == null) null
// TODO: Switch to multi-resolve in this API
else ResolveImportUtil.resolveChild(seeker, component, context.footholdFile, !context.withMembers,
!context.withPlainDirectories)
}
}
/**
* Creates a [PyQualifiedNameResolveContext] from a [foothold] element.
*/
fun fromFoothold(foothold: PsiElement): PyQualifiedNameResolveContext {
val module = ModuleUtilCore.findModuleForPsiElement(foothold)
val sdk = module?.let { ModuleRootManager.getInstance(module).sdk }
return PyQualifiedNameResolveContextImpl(foothold.manager, module, foothold, sdk)
}
/**
* Creates a [PyQualifiedNameResolveContext] from a [module].
*/
fun fromModule(module: Module): PyQualifiedNameResolveContext =
PyQualifiedNameResolveContextImpl(PsiManager.getInstance(module.project), module, null, ModuleRootManager.getInstance(module).sdk)
/**
* Creates a [PyQualifiedNameResolveContext] from an [sdk].
*/
fun fromSdk(project: Project, sdk: Sdk): PyQualifiedNameResolveContext =
PyQualifiedNameResolveContextImpl(PsiManager.getInstance(project), module = null, foothold = null, sdk = sdk)
private fun foreignResults(name: QualifiedName, context: PyQualifiedNameResolveContext) =
if (context.withoutForeign)
emptyList()
else
Extensions.getExtensions(PyImportResolver.EP_NAME)
.asSequence()
.map { it.resolveImportReference(name, context, !context.withoutRoots) }
.filterNotNull()
.toList()
private fun relativeResultsFromSkeletons(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
val footholdFile = context.footholdFile
if (context.withoutRoots && footholdFile != null) {
val virtualFile = footholdFile.virtualFile
if (virtualFile == null || FileIndexFacade.getInstance(context.project).isInContent(virtualFile)) {
return emptyList()
}
val containingDirectory = context.containingDirectory
if (containingDirectory != null) {
val containingName = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null)
if (containingName != null && containingName.componentCount > 0) {
val absoluteName = containingName.append(name)
val sdk = PythonSdkType.getSdk(footholdFile) ?: return emptyList()
val skeletonsVirtualFile = PySdkUtil.findSkeletonsDir(sdk) ?: return emptyList()
val skeletonsDir = context.psiManager.findDirectory(skeletonsVirtualFile)
return listOfNotNull(resolveModuleAt(absoluteName, skeletonsDir, context))
}
}
}
return emptyList()
}
/**
* Filters the results according to their import priority in sys.path.
*/
private fun findFirstResults(results: List<PsiElement>) =
if (results.all(::isNamespacePackage))
results
else
listOfNotNull(results.firstOrNull { !isNamespacePackage(it) })
private fun isNamespacePackage(element: PsiElement): Boolean {
if (element is PsiDirectory) {
val level = PyUtil.getLanguageLevelForVirtualFile(element.project, element.virtualFile)
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
return PyUtil.turnDirIntoInit(element) == null
}
}
return false
}
private fun resolveWithRelativeLevel(name: QualifiedName, context : PyQualifiedNameResolveContext): PsiElement? {
val footholdFile = context.footholdFile
if (context.relativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
return resolveModuleAt(name, context.containingDirectory, context)
}
return null
}
private fun resultsFromRoots(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
if (context.withoutRoots) {
return emptyList()
}
val moduleResults = mutableListOf<PsiElement>()
val sdkResults = mutableListOf<PsiElement>()
val visitor = RootVisitor { root, module, sdk, isModuleSource ->
val results = if (isModuleSource) moduleResults else sdkResults
if (!root.isValid || root == PyUserSkeletonsUtil.getUserSkeletonsDirectory()) {
return@RootVisitor true
}
val result = resolveInRoot(name, root, context)
if (result != null) {
results.add(result)
}
if (isAcceptRootAsTopLevelPackage(context) && name.matchesPrefix(QualifiedName.fromDottedString(root.name))) {
val topLevelResult = resolveInRoot(name, root.parent, context)
if (topLevelResult != null) {
results.add(topLevelResult)
}
}
return@RootVisitor true
}
val sdk = context.effectiveSdk
val module = context.module
val footholdFile = context.footholdFile
when {
context.visitAllModules -> {
ModuleManager.getInstance(context.project).modules.forEach {
RootVisitorHost.visitRoots(it, true, visitor)
}
when {
sdk != null ->
RootVisitorHost.visitSdkRoots(sdk, visitor)
footholdFile != null ->
RootVisitorHost.visitSdkRoots(footholdFile, visitor)
}
}
module != null -> {
val otherSdk = sdk != context.sdk
RootVisitorHost.visitRoots(module, otherSdk, visitor)
if (otherSdk && sdk != null) {
RootVisitorHost.visitSdkRoots(sdk, visitor)
}
}
footholdFile != null -> {
RootVisitorHost.visitRoots(footholdFile, visitor)
}
sdk != null -> {
RootVisitorHost.visitSdkRoots(sdk, visitor)
}
else -> throw IllegalStateException()
}
return moduleResults + sdkResults
}
private fun isAcceptRootAsTopLevelPackage(context: PyQualifiedNameResolveContext): Boolean {
context.module?.let {
FacetManager.getInstance(it).allFacets.forEach {
if (it is PythonPathContributingFacet && it.acceptRootAsTopLevelPackage()) {
return true
}
}
}
return false
}
private fun resolveInRoot(name: QualifiedName, root: VirtualFile, context: PyQualifiedNameResolveContext): PsiElement? {
return if (root.isDirectory) resolveModuleAt(name, context.psiManager.findDirectory(root), context) else null
}
private fun findCache(context: PyQualifiedNameResolveContext): PythonPathCache? {
return when {
context.visitAllModules -> null
context.module != null ->
if (context.effectiveSdk != context.sdk) null else PythonModulePathCache.getInstance(context.module)
context.footholdFile != null -> {
val sdk = PyBuiltinCache.findSdkForNonModuleFile(context.footholdFile)
if (sdk != null) PythonSdkPathCache.getInstance(context.project, sdk) else null
}
else -> null
}
}
private fun isRelativeImportResult(name: QualifiedName, directory: PsiDirectory, result: PsiElement,
context: PyQualifiedNameResolveContext): Boolean {
if (context.relativeLevel > 0) {
return true
}
else {
val py2 = LanguageLevel.forElement(directory).isOlderThan(LanguageLevel.PYTHON30)
return context.relativeLevel == 0 && py2 && PyUtil.isPackage(directory, false, null) &&
result is PsiFileSystemItem && name != QualifiedNameFinder.findShortestImportableQName(result)
}
}
private fun checkAccess() {
Preconditions.checkState(ApplicationManager.getApplication().isReadAccessAllowed, "This method requires read access")
}
@@ -1,521 +0,0 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.resolve;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
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.*;
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;
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;
import java.util.*;
import static com.jetbrains.python.psi.PyUtil.as;
import static com.jetbrains.python.psi.PyUtil.turnDirIntoInit;
/**
* Resolves the specified qualified name in the specified context (module, all modules or a file) to a file or directory.
*
* @author yole
*/
public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameResolver {
boolean myCheckForPackage = true;
private final QualifiedNameResolveContext myContext = new QualifiedNameResolveContext();
private final @NotNull QualifiedName myQualifiedName;
final Set<PsiElement> mySourceResults = Sets.newLinkedHashSet();
final Set<PsiElement> myLibResults = Sets.newLinkedHashSet();
final Set<PsiElement> myForeignResults = Sets.newLinkedHashSet();
private boolean myVisitAllModules = false;
private int myRelativeLevel = -1;
private boolean myWithoutRoots;
private boolean myWithoutForeign;
private boolean myWithMembers;
public QualifiedNameResolverImpl(@NotNull String qNameString) {
myQualifiedName = QualifiedName.fromDottedString(qNameString);
}
/**
* @param qName the empty name means that all found roots will be traversed.
*/
public QualifiedNameResolverImpl(@NotNull QualifiedName qName) {
myQualifiedName = qName;
}
@Override
public QualifiedNameResolver withContext(QualifiedNameResolveContext context) {
myContext.copyFrom(context);
return this;
}
@Override
public QualifiedNameResolver fromElement(@NotNull PsiElement foothold) {
checkAccess();
myContext.setFromElement(foothold);
if (PydevConsoleRunner.isInPydevConsole(foothold) || PyUtil.isInScratchFile(foothold)) {
withAllModules();
Sdk sdk = PydevConsoleRunner.getConsoleSdk(foothold);
if (sdk != null) {
myContext.setSdk(sdk);
}
}
return this;
}
@Override
public QualifiedNameResolver fromModule(@NotNull Module module) {
PyPsiUtils.assertValid(module);
checkAccess();
myContext.setFromModule(module);
return this;
}
@Override
public QualifiedNameResolver fromSdk(@NotNull Project project, @NotNull Sdk sdk) {
myContext.setFromSdk(project, sdk);
return this;
}
private boolean isAcceptRootAsTopLevelPackage() {
Module module = myContext.getModule();
if (module != null) {
Facet[] facets = FacetManager.getInstance(module).getAllFacets();
for (Facet facet : facets) {
if (facet instanceof PythonPathContributingFacet && ((PythonPathContributingFacet)facet).acceptRootAsTopLevelPackage()) {
return true;
}
}
}
return false;
}
@Override
public QualifiedNameResolver withAllModules() {
myVisitAllModules = true;
return this;
}
/**
* Specifies that we need to look for the name in the specified SDK (instead of the SDK assigned to the module, if any).
*
* @param sdk the SDK in which the name should be searched.
* @return this
*/
@Override
public QualifiedNameResolver withSdk(Sdk sdk) {
myContext.setSdk(sdk);
return this;
}
/**
* Specifies whether we should attempt to resolve imports relative to the current file.
*
* @param relativeLevel if >= 0, we try to resolve at the specified number of levels above the current file.
* @return this
*/
@Override
public QualifiedNameResolver withRelative(int relativeLevel) {
myRelativeLevel = relativeLevel;
return this;
}
/**
* Specifies that we should only try to resolve relative to the current file, not in roots.
*
* @return this
*/
@Override
public QualifiedNameResolver withoutRoots() {
myWithoutRoots = true;
return this;
}
@Override
public QualifiedNameResolver withoutForeign() {
myWithoutForeign = true;
return this;
}
@Override
public QualifiedNameResolver withMembers() {
myWithMembers = true;
return this;
}
/**
* Specifies that we're looking for a file in a directory hierarchy, not a module in the Python package hierarchy
* (so we don't need to check for existence of __init__.py)
*
* @return
*/
@Override
public QualifiedNameResolver withPlainDirectories() {
myCheckForPackage = false;
return this;
}
public boolean visitRoot(final VirtualFile root, @Nullable Module module, @Nullable Sdk sdk, boolean isModuleSource) {
checkAccess();
if (!root.isValid()) {
return true;
}
if (root.equals(PyUserSkeletonsUtil.getUserSkeletonsDirectory())) {
return true;
}
PsiElement resolveResult = resolveInRoot(root);
if (resolveResult != null) {
addRoot(resolveResult, isModuleSource);
}
if (isAcceptRootAsTopLevelPackage() && myQualifiedName.matchesPrefix(QualifiedName.fromDottedString(root.getName()))) {
resolveResult = resolveInRoot(root.getParent());
if (resolveResult != null) {
addRoot(resolveResult, isModuleSource);
}
}
return true;
}
private void addRoot(PsiElement resolveResult, boolean isModuleSource) {
final Set<PsiElement> results = isModuleSource ? mySourceResults : myLibResults;
final boolean allNamespacePackages = allNamespacePackages(results);
if (allNamespacePackages) {
if (!isNamespacePackage(resolveResult)) {
results.clear();
}
}
if (allNamespacePackages || results.isEmpty() || myQualifiedName.getComponentCount() == 0) {
results.add(resolveResult);
}
}
private static boolean isNamespacePackage(@NotNull PsiElement element) {
final PsiDirectory dir = as(element, PsiDirectory.class);
if (dir != null) {
final LanguageLevel level = PyUtil.getLanguageLevelForVirtualFile(dir.getProject(), dir.getVirtualFile());
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
return turnDirIntoInit(dir) == null;
}
}
return false;
}
private static boolean allNamespacePackages(@NotNull Collection<PsiElement> elements) {
for (PsiElement element : elements) {
if (!isNamespacePackage(element)) {
return false;
}
}
return true;
}
@Override
@NotNull
public List<PsiElement> resultsAsList() {
checkAccess();
if (!myContext.isValid()) {
return Collections.emptyList();
}
final PsiFile footholdFile = myContext.getFootholdFile();
checkValidForTests(footholdFile);
boolean foundRelativeImport = false;
if (myRelativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
PsiDirectory dir = footholdFile.getContainingDirectory();
checkValidForTests(dir);
if (myRelativeLevel > 0) {
dir = ResolveImportUtil.stepBackFrom(footholdFile, myRelativeLevel);
checkValidForTests(dir);
}
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 && !foundRelativeImport;
if (mayCache) {
final List<PsiElement> cachedResults = cache.get(myQualifiedName);
if (cachedResults != null) {
cachedResults.stream().forEach(QualifiedNameResolverImpl::checkValidForTests);
mySourceResults.addAll(cachedResults);
return Lists.newArrayList(mySourceResults);
}
}
if (!myWithoutRoots) {
addResultsFromRoots();
}
else if (footholdFile != null) {
addRelativeImportResultsFromSkeletons(footholdFile);
}
if (mySourceResults.isEmpty() || myQualifiedName.getComponentCount() == 0) {
mySourceResults.addAll(myLibResults);
myLibResults.clear();
}
if (!myWithoutForeign) {
if (mySourceResults.isEmpty() || myQualifiedName.getComponentCount() == 0) {
for (PyImportResolver resolver : Extensions.getExtensions(PyImportResolver.EP_NAME)) {
PsiElement foreign = resolver.resolveImportReference(myQualifiedName, myContext, !myWithoutRoots);
if (foreign != null) {
myForeignResults.add(foreign);
}
}
mySourceResults.addAll(myForeignResults);
myForeignResults.clear();
}
}
final ArrayList<PsiElement> results = Lists.newArrayList(mySourceResults);
if (mayCache) {
cache.put(myQualifiedName, results);
}
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
*/
private void addRelativeImportResultsFromSkeletons(@NotNull final PsiFile foothold) {
final VirtualFile vFile = foothold.getVirtualFile();
if (vFile == null || FileIndexFacade.getInstance(foothold.getProject()).isInContent(vFile)) {
return;
}
PsiDirectory containingDirectory = foothold.getContainingDirectory();
if (myRelativeLevel > 0) {
containingDirectory = ResolveImportUtil.stepBackFrom(foothold, myRelativeLevel);
}
if (containingDirectory != null) {
final QualifiedName containingQName = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null);
if (containingQName != null && containingQName.getComponentCount() > 0) {
final QualifiedName absoluteQName = containingQName.append(myQualifiedName.toString());
final QualifiedNameResolverImpl absoluteVisitor =
(QualifiedNameResolverImpl)new QualifiedNameResolverImpl(absoluteQName).fromElement(foothold);
final Sdk sdk = PythonSdkType.getSdk(foothold);
if (sdk == null) return;
final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(sdk);
if (skeletonsDir == null) return;
final PsiDirectory directory = myContext.getPsiManager().findDirectory(skeletonsDir);
final PsiElement psiElement = absoluteVisitor.resolveModuleAt(directory);
if (psiElement != null) {
addRoot(psiElement, false);
}
}
}
}
private void addResultsFromRoots() {
if (myVisitAllModules) {
for (Module mod : ModuleManager.getInstance(myContext.getProject()).getModules()) {
RootVisitorHost.visitRoots(mod, true, this);
}
if (myContext.getSdk() != null) {
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
}
else if (myContext.getFootholdFile() != null) {
RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
}
}
else if (myContext.getModule() != null) {
final boolean otherSdk = withOtherSdk();
RootVisitorHost.visitRoots(myContext.getModule(), otherSdk, this);
if (otherSdk) {
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
}
}
else if (myContext.getFootholdFile() != null) {
RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
}
else if (myContext.getSdk() != null) {
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
}
else {
throw new IllegalStateException("Context is empty. Provide some context: module, file, sdk, etc");
}
}
@Override
@Nullable
public PsiElement firstResult() {
checkAccess();
final List<PsiElement> results = resultsAsList();
return results.size() > 0 ? results.get(0) : null;
}
@Override
@NotNull
public <T extends PsiElement> List<T> resultsOfType(Class<T> clazz) {
checkAccess();
List<T> result = new ArrayList<>();
for (PsiElement element : resultsAsList()) {
if (clazz.isInstance(element)) {
//noinspection unchecked
result.add((T)element);
}
}
return result;
}
@Override
@Nullable
public <T extends PsiElement> T firstResultOfType(Class<T> clazz) {
checkAccess();
final List<T> list = resultsOfType(clazz);
return list.size() > 0 ? list.get(0) : null;
}
private boolean withOtherSdk() {
return myContext.getSdk() != null && myContext.getSdk() != PythonSdkType.findPythonSdk(myContext.getModule());
}
@Nullable
private PythonPathCache findMyCache() {
if (myVisitAllModules) {
return null;
}
if (myContext.getModule() != null) {
return withOtherSdk() ? null : PythonModulePathCache.getInstance(myContext.getModule());
}
if (myContext.getFootholdFile() != null) {
final Sdk sdk = PyBuiltinCache.findSdkForNonModuleFile(myContext.getFootholdFile());
if (sdk != null) {
return PythonSdkPathCache.getInstance(myContext.getProject(), sdk);
}
}
return null;
}
@Nullable
private PsiElement resolveInRoot(VirtualFile root) {
if (!root.isDirectory()) {
// if we have added a file as a root, it's unlikely that we'll be able to resolve anything under it in 'files only' resolve mode
return null;
}
return resolveModuleAt(myContext.getPsiManager().findDirectory(root));
}
/**
* Searches for a module at given directory, unwinding qualifiers and traversing directories as needed.
*
* @param directory where to start from; top qualifier will be searched for here.
*/
@Contract("null -> null")
@Nullable
public PsiElement resolveModuleAt(@Nullable PsiDirectory directory) {
// prerequisites
checkAccess();
PyPsiUtils.assertValid(directory);
if (directory == null || !directory.isValid()) return null;
PsiElement seeker = directory;
for (String name : myQualifiedName.getComponents()) {
if (name == null) {
return null;
}
seeker = ResolveImportUtil.resolveChild(seeker, name, myContext.getFootholdFile(), !myWithMembers, myCheckForPackage);
}
return seeker;
}
@Override
public Module getModule() {
return myContext.getModule();
}
@Nullable
@Override
public <T extends PsiNamedElement> T resolveTopLevelMember(@NotNull final Class<T> aClass) {
checkAccess();
Preconditions.checkState(getModule() != null, "Module is not set");
final String memberName = myQualifiedName.getLastComponent();
if (memberName == null) {
return null;
}
final PyFile file =
new QualifiedNameResolverImpl(myQualifiedName.removeLastComponent()).fromModule(getModule()).firstResultOfType(PyFile.class);
if (file == null) {
return null;
}
checkValidForTests(file);
for (final T element : PsiTreeUtil.getChildrenOfTypeAsList(file, aClass)) {
checkValidForTests(element);
if (memberName.equals(element.getName())) {
return element;
}
}
return null;
}
private static void checkValidForTests(@Nullable final PsiElement element) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
PyPsiUtils.assertValid(element);
}
}
private static void checkAccess() {
Preconditions.checkState(ApplicationManager.getApplication().isReadAccessAllowed(), "This method requires read access");
}
}
@@ -16,7 +16,6 @@
package com.jetbrains.python.psi.resolve;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
import com.intellij.openapi.fileTypes.FileNameMatcher;
@@ -209,18 +208,11 @@ public class ResolveImportUtil {
}
try {
beingImported.add(marker);
final QualifiedNameResolver visitor = new QualifiedNameResolverImpl(qualifiedName).fromElement(sourceFile);
if (relativeLevel > 0) {
// "from ...module import"
visitor.withRelative(relativeLevel).withoutRoots();
}
else {
// "from module import"
if (!importIsAbsolute) {
visitor.withRelative(0);
}
}
return visitor.resultsAsList();
final PyQualifiedNameResolveContext initialContext = PyResolveImportUtil.fromFoothold(sourceFile);
final PyQualifiedNameResolveContext context = relativeLevel > 0 ?
initialContext.copyWithRelative(relativeLevel).copyWithoutRoots() :
importIsAbsolute ? initialContext : initialContext.copyWithRelative(0);
return PyResolveImportUtil.resolveQualifiedName(qualifiedName, context);
}
finally {
beingImported.remove(marker);
@@ -230,8 +222,9 @@ public class ResolveImportUtil {
@Nullable
public static PsiElement resolveModuleInRoots(@NotNull QualifiedName moduleQualifiedName, @Nullable PsiElement foothold) {
if (foothold == null) return null;
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(moduleQualifiedName).fromElement(foothold);
return visitor.firstResult();
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(moduleQualifiedName,
PyResolveImportUtil.fromFoothold(foothold));
return !results.isEmpty() ? results.get(0) : null;
}
@Nullable
@@ -367,7 +360,9 @@ public class ResolveImportUtil {
@Nullable
private static PsiElement resolveForeignImports(@NotNull PsiFile foothold, @NotNull String referencedName) {
return new QualifiedNameResolverImpl(referencedName).fromElement(foothold).withoutRoots().firstResult();
final PyQualifiedNameResolveContext context = PyResolveImportUtil.fromFoothold(foothold).copyWithoutRoots();
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(referencedName), context);
return !results.isEmpty() ? results.get(0) : null;
}
@NotNull
@@ -35,7 +35,7 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.types.functionalParser.ForwardDeclaration;
import com.jetbrains.python.psi.types.functionalParser.FunctionalParser;
@@ -631,7 +631,8 @@ public class PyTypeParser {
final Token<PyElementType> token = tokens.get(0);
final String name = token.getText().toString();
qName = qName != null ? qName.append(name) : QualifiedName.fromComponents(name);
PsiElement module = new QualifiedNameResolverImpl(qName).fromElement(myAnchor).firstResult();
final List<PsiElement> modules = PyResolveImportUtil.resolveQualifiedName(qName, PyResolveImportUtil.fromFoothold(myAnchor));
PsiElement module = !modules.isEmpty() ? modules.get(0) : null;
if (module == null) {
break;
}