blob: db5a9ba3f1aeeacaf24a75b9fe1fb252e59477fa [file] [log] [blame]
/*
* 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.types;
import com.google.common.collect.ImmutableSet;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.PyDynamicMember;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.impl.ResolveResultList;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.jetbrains.python.psi.PyUtil.inSameFile;
/**
* @author yole
*/
public class PyModuleType implements PyType { // Modules don't descend from object
@NotNull private final PyFile myModule;
@Nullable private final PyImportedModule myImportedModule;
public static final ImmutableSet<String> MODULE_MEMBERS = ImmutableSet.of(
"__name__", "__file__", "__path__", "__doc__", "__dict__", "__package__");
public PyModuleType(@NotNull PyFile source) {
this(source, null);
}
public PyModuleType(@NotNull PyFile source, @Nullable PyImportedModule importedModule) {
myModule = source;
myImportedModule = importedModule;
}
@NotNull
public PyFile getModule() {
return myModule;
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull final String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
final PsiElement overridingMember = resolveByOverridingMembersProviders(myModule, name);
if (overridingMember != null) {
return ResolveResultList.to(overridingMember);
}
final PsiElement attribute = myModule.getElementNamed(name);
if (attribute != null) {
return ResolveResultList.to(attribute);
}
if (PyUtil.isPackage(myModule)) {
final List<PyImportElement> importElements = new ArrayList<PyImportElement>();
if (myImportedModule != null && (location == null || !inSameFile(location, myImportedModule))) {
final PyImportElement importElement = myImportedModule.getImportElement();
if (importElement != null) {
importElements.add(importElement);
}
}
else if (location != null) {
final ScopeOwner owner = ScopeUtil.getScopeOwner(location);
if (owner != null) {
importElements.addAll(getVisibleImports(owner));
}
if (!inSameFile(location, myModule)) {
importElements.addAll(myModule.getImportTargets());
}
}
final List<? extends RatedResolveResult> implicitMembers = resolveImplicitPackageMember(name, importElements);
if (implicitMembers != null) {
return implicitMembers;
}
}
final PsiElement member = resolveByMembersProviders(myModule, name);
if (member != null) {
return ResolveResultList.to(member);
}
return null;
}
@Nullable
private static PsiElement resolveByMembersProviders(PyFile module, String name) {
for (PyModuleMembersProvider provider : Extensions.getExtensions(PyModuleMembersProvider.EP_NAME)) {
if (!(provider instanceof PyOverridingModuleMembersProvider)) {
final PsiElement element = provider.resolveMember(module, name);
if (element != null) {
return element;
}
}
}
return null;
}
@Nullable
private static PsiElement resolveByOverridingMembersProviders(@NotNull PyFile module, @NotNull String name) {
for (PyModuleMembersProvider provider : Extensions.getExtensions(PyModuleMembersProvider.EP_NAME)) {
if (provider instanceof PyOverridingModuleMembersProvider) {
final PsiElement element = provider.resolveMember(module, name);
if (element != null) {
return element;
}
}
}
return null;
}
@Nullable
private List<? extends RatedResolveResult> resolveImplicitPackageMember(@NotNull String name,
@NotNull List<PyImportElement> importElements) {
final VirtualFile moduleFile = myModule.getVirtualFile();
if (moduleFile != null) {
for (QualifiedName packageQName : QualifiedNameFinder.findImportableQNames(myModule, myModule.getVirtualFile())) {
final QualifiedName resolvingQName = packageQName.append(name);
for (PyImportElement importElement : importElements) {
for (QualifiedName qName : getImportedQNames(importElement)) {
if (qName.matchesPrefix(resolvingQName)) {
final PsiElement subModule = ResolveImportUtil.resolveChild(myModule, name, myModule, false, true);
if (subModule != null) {
final ResolveResultList results = new ResolveResultList();
results.add(new ImportedResolveResult(subModule, RatedResolveResult.RATE_NORMAL,
Collections.<PsiElement>singletonList(importElement)));
return results;
}
}
}
}
}
}
return null;
}
@NotNull
private static List<QualifiedName> getImportedQNames(@NotNull PyImportElement element) {
final List<QualifiedName> importedQNames = new ArrayList<QualifiedName>();
final PyStatement stmt = element.getContainingImportStatement();
if (stmt instanceof PyFromImportStatement) {
final PyFromImportStatement fromImportStatement = (PyFromImportStatement)stmt;
final QualifiedName importedQName = fromImportStatement.getImportSourceQName();
final String visibleName = element.getVisibleName();
if (importedQName != null) {
importedQNames.add(importedQName);
final QualifiedName implicitSubModuleQName = importedQName.append(visibleName);
if (implicitSubModuleQName != null) {
importedQNames.add(implicitSubModuleQName);
}
}
}
else if (stmt instanceof PyImportStatement) {
final QualifiedName importedQName = element.getImportedQName();
if (importedQName != null) {
importedQNames.add(importedQName);
}
}
if (!ResolveImportUtil.isAbsoluteImportEnabledFor(element)) {
PsiFile file = element.getContainingFile();
if (file != null) {
file = file.getOriginalFile();
}
final QualifiedName absoluteQName = QualifiedNameFinder.findShortestImportableQName(file);
if (file != null && absoluteQName != null) {
final QualifiedName prefixQName = PyUtil.isPackage(file) ? absoluteQName : absoluteQName.removeLastComponent();
if (prefixQName.getComponentCount() > 0) {
final List<QualifiedName> results = new ArrayList<QualifiedName>();
results.addAll(importedQNames);
for (QualifiedName qName : importedQNames) {
final List<String> components = new ArrayList<String>();
components.addAll(prefixQName.getComponents());
components.addAll(qName.getComponents());
results.add(QualifiedName.fromComponents(components));
}
return results;
}
}
}
return importedQNames;
}
@NotNull
public static List<PyImportElement> getVisibleImports(@NotNull ScopeOwner owner) {
final List<PyImportElement> visibleImports = new ArrayList<PyImportElement>();
PyResolveUtil.scopeCrawlUp(new PsiScopeProcessor() {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PyImportElement) {
visibleImports.add((PyImportElement)element);
}
return true;
}
@Nullable
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
return null;
}
@Override
public void handleEvent(@NotNull Event event, @Nullable Object associated) {
}
}, owner, null, null);
return visibleImports;
}
/**
* @param directory the module directory
*
* @return a list of submodules of the specified module directory, either files or dirs, for easier naming; may contain file names
* not suitable for import.
*/
@NotNull
private static List<PsiFileSystemItem> getSubmodulesList(final PsiDirectory directory) {
List<PsiFileSystemItem> result = new ArrayList<PsiFileSystemItem>();
if (directory != null) { // just in case
// file modules
for (PsiFile f : directory.getFiles()) {
final String filename = f.getName();
// if we have a binary module, we'll most likely also have a stub for it in site-packages
if ((f instanceof PyFile && !filename.equals(PyNames.INIT_DOT_PY)) || isBinaryModule(filename)) {
result.add(f);
}
}
// dir modules
for (PsiDirectory dir : directory.getSubdirectories()) {
if (dir.findFile(PyNames.INIT_DOT_PY) instanceof PyFile) result.add(dir);
}
}
return result;
}
private static boolean isBinaryModule(String filename) {
final String ext = FileUtilRt.getExtension(filename);
if (SystemInfo.isWindows) {
return "pyd".equalsIgnoreCase(ext);
}
else {
return "so".equals(ext);
}
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
List<LookupElement> result = getCompletionVariantsAsLookupElements(location, context, false, false);
return result.toArray();
}
public List<LookupElement> getCompletionVariantsAsLookupElements(PsiElement location,
ProcessingContext context,
boolean wantAllSubmodules, boolean suppressParentheses) {
List<LookupElement> result = new ArrayList<LookupElement>();
Set<String> namesAlready = context.get(CTX_NAMES);
PointInImport point = ResolveImportUtil.getPointInImport(location);
for (PyModuleMembersProvider provider : Extensions.getExtensions(PyModuleMembersProvider.EP_NAME)) {
for (PyDynamicMember member : provider.getMembers(myModule, point)) {
final String name = member.getName();
if (namesAlready != null) {
namesAlready.add(name);
}
if (PyUtil.isClassPrivateName(name)) {
continue;
}
result.add(LookupElementBuilder.create(name).withIcon(member.getIcon()).withTypeText(member.getShortType()));
}
}
if (point == PointInImport.NONE || point == PointInImport.AS_NAME) { // when not imported from, add regular attributes
final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(location, new Condition<PsiElement>() {
@Override
public boolean value(PsiElement psiElement) {
return !(psiElement instanceof PyImportElement) ||
PsiTreeUtil.getParentOfType(psiElement, PyImportStatementBase.class) instanceof PyFromImportStatement;
}
}, null);
if (suppressParentheses) {
processor.suppressParentheses();
}
processor.setPlainNamesOnly(point == PointInImport.AS_NAME); // no parens after imported function names
myModule.processDeclarations(processor, ResolveState.initial(), null, location);
if (namesAlready != null) {
for (LookupElement le : processor.getResultList()) {
String name = le.getLookupString();
if (!namesAlready.contains(name)) {
result.add(le);
namesAlready.add(name);
}
}
}
else {
result.addAll(processor.getResultList());
}
}
if (PyUtil.isPackage(myModule)) { // our module is a dir, not a single file
if (point == PointInImport.AS_MODULE || point == PointInImport.AS_NAME || wantAllSubmodules) { // when imported from somehow, add submodules
result.addAll(getSubModuleVariants(myModule.getContainingDirectory(), location, namesAlready));
}
else {
addImportedSubmodules(location, namesAlready, result);
}
}
return result;
}
private void addImportedSubmodules(PsiElement location, Set<String> existingNames, List<LookupElement> result) {
PsiFile file = location.getContainingFile();
if (file instanceof PyFile) {
PyFile pyFile = (PyFile)file;
PsiElement moduleBase = PyUtil.isPackage(myModule) ? myModule.getContainingDirectory() : myModule;
for (PyImportElement importElement : pyFile.getImportTargets()) {
PsiElement target = PyUtil.turnInitIntoDir(importElement.resolve());
if (target != null && PsiTreeUtil.isAncestor(moduleBase, target, true)) {
LookupElement element = null;
if (target instanceof PsiFileSystemItem) {
element = buildFileLookupElement((PsiFileSystemItem) target, existingNames);
}
else if (target instanceof PsiNamedElement) {
element = LookupElementBuilder.createWithIcon((PsiNamedElement)target);
}
if (element != null) {
result.add(element);
}
}
}
}
}
public static List<LookupElement> getSubModuleVariants(final PsiDirectory directory,
PsiElement location,
Set<String> namesAlready) {
List<LookupElement> result = new ArrayList<LookupElement>();
for (PsiFileSystemItem item : getSubmodulesList(directory)) {
if (item != location.getContainingFile().getOriginalFile()) {
LookupElement lookupElement = buildFileLookupElement(item, namesAlready);
if (lookupElement != null) {
result.add(lookupElement);
}
}
}
return result;
}
@Nullable
public static LookupElementBuilder buildFileLookupElement(PsiFileSystemItem item, @Nullable Set<String> existingNames) {
String s = FileUtil.getNameWithoutExtension(item.getName());
if (!PyNames.isIdentifier(s)) return null;
if (existingNames != null) {
if (existingNames.contains(s)) return null;
else existingNames.add(s);
}
return LookupElementBuilder.create(item, s)
.withTypeText(getPresentablePath((PsiDirectory)item.getParent()))
.withPresentableText(s)
.withIcon(item.getIcon(0));
}
private static String getPresentablePath(PsiDirectory directory) {
if (directory == null) {
return "";
}
final String path = directory.getVirtualFile().getPath();
if (path.contains(PythonSdkType.SKELETON_DIR_NAME)) {
return "<built-in>";
}
return FileUtil.toSystemDependentName(path);
}
@Override
public String getName() {
return myModule.getName();
}
@Override
public boolean isBuiltin() {
return true;
}
@Override
public void assertValid(String message) {
if (!myModule.isValid()) {
throw new PsiInvalidElementAccessException(myModule, myModule.getClass().toString() + ": " + message);
}
}
@NotNull
public static Set<String> getPossibleInstanceMembers() {
return MODULE_MEMBERS;
}
}