blob: a537dae722c1e9fd8bb332a400d37bc5e4446272 [file] [log] [blame]
/*
* Copyright 2000-2013 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.impl.references;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.stubs.StubUpdatingIndex;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.ResolveResultList;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.search.PyProjectScopeBuilder;
import com.jetbrains.python.psi.stubs.PyClassNameIndexInsensitive;
import com.jetbrains.python.psi.stubs.PyFunctionNameIndex;
import com.jetbrains.python.psi.stubs.PyInstanceAttributeIndex;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class PyQualifiedReference extends PyReferenceImpl {
private static final Logger LOG = Logger.getInstance(PyQualifiedReference.class);
public PyQualifiedReference(PyQualifiedExpression element, PyResolveContext context) {
super(element, context);
}
@NotNull
@Override
protected List<RatedResolveResult> resolveInner() {
ResolveResultList ret = new ResolveResultList();
final String referencedName = myElement.getReferencedName();
if (referencedName == null) return ret;
final PyExpression qualifier = myElement.getQualifier();
if (qualifier == null) {
return ret;
}
// regular attributes
PyType qualifierType = myContext.getTypeEvalContext().getType(qualifier);
// is it a class-private name qualified by a different class?
if (PyUtil.isClassPrivateName(referencedName) && qualifierType instanceof PyClassType) {
if (isOtherClassQualifying(qualifier, (PyClassType)qualifierType)) return Collections.emptyList();
}
//
if (qualifierType != null) {
qualifierType.assertValid("qualifier: " + qualifier);
// resolve within the type proper
AccessDirection ctx = AccessDirection.of(myElement);
final List<? extends RatedResolveResult> membersOfQualifier = qualifierType.resolveMember(referencedName, qualifier, ctx, myContext);
if (membersOfQualifier == null) {
return ret; // qualifier is positive that such name cannot exist in it
}
ret.addAll(membersOfQualifier);
}
// look for assignment of this attribute in containing function
if (qualifier instanceof PyQualifiedExpression && ret.isEmpty()) {
if (addAssignedAttributes(ret, referencedName, (PyQualifiedExpression)qualifier)) {
return ret;
}
}
if (PyTypeChecker.isUnknown(qualifierType) &&
myContext.allowImplicits() && canQualifyAnImplicitName(qualifier, qualifierType)) {
addImplicitResolveResults(referencedName, ret);
}
// special case of __doc__
if ("__doc__".equals(referencedName)) {
addDocReference(ret, qualifier, qualifierType);
}
return ret;
}
private static boolean isOtherClassQualifying(PyExpression qualifier, PyClassType qualifierType) {
final List<? extends PsiElement> match = PyUtil.searchForWrappingMethod(qualifier, true);
if (match == null) {
return true;
}
if (match.size() > 1) {
final PyClass ourClass = qualifierType.getPyClass();
final PsiElement theirClass = CompletionUtil.getOriginalOrSelf(match.get(match.size() - 1));
if (ourClass != theirClass) return true;
}
return false;
}
private void addImplicitResolveResults(String referencedName, ResolveResultList ret) {
final Project project = myElement.getProject();
final GlobalSearchScope scope = PyProjectScopeBuilder.excludeSdkTestsScope(project);
final Collection functions = PyFunctionNameIndex.find(referencedName, project, scope);
final PsiFile containingFile = myElement.getContainingFile();
final List<QualifiedName> imports;
if (containingFile instanceof PyFile) {
imports = collectImports((PyFile)containingFile);
}
else {
imports = Collections.emptyList();
}
for (Object function : functions) {
if (!(function instanceof PyFunction)) {
FileBasedIndex.getInstance().scheduleRebuild(StubUpdatingIndex.INDEX_ID,
new Throwable("found non-function object " + function + " in function list"));
break;
}
PyFunction pyFunction = (PyFunction)function;
if (pyFunction.getContainingClass() != null) {
ret.add(new ImplicitResolveResult(pyFunction, getImplicitResultRate(pyFunction, imports)));
}
}
final Collection attributes = PyInstanceAttributeIndex.find(referencedName, project, scope);
for (Object attribute : attributes) {
if (!(attribute instanceof PyTargetExpression)) {
FileBasedIndex.getInstance().scheduleRebuild(StubUpdatingIndex.INDEX_ID,
new Throwable(
"found non-target expression object " + attribute + " in target expression list"));
break;
}
ret.add(new ImplicitResolveResult((PyTargetExpression)attribute, getImplicitResultRate((PyTargetExpression)attribute, imports)));
}
}
private static List<QualifiedName> collectImports(PyFile containingFile) {
List<QualifiedName> imports = new ArrayList<QualifiedName>();
for (PyFromImportStatement anImport : containingFile.getFromImports()) {
final QualifiedName source = anImport.getImportSourceQName();
if (source != null) {
imports.add(source);
}
}
for (PyImportElement importElement : containingFile.getImportTargets()) {
final QualifiedName qName = importElement.getImportedQName();
if (qName != null) {
imports.add(qName.removeLastComponent());
}
}
return imports;
}
private int getImplicitResultRate(PyElement target, List<QualifiedName> imports) {
int rate = RatedResolveResult.RATE_LOW;
if (target.getContainingFile() == myElement.getContainingFile()) {
rate += 200;
}
else {
final VirtualFile vFile = target.getContainingFile().getVirtualFile();
if (vFile != null) {
if (ProjectScope.getProjectScope(myElement.getProject()).contains(vFile)) {
rate += 80;
}
final QualifiedName qName = QualifiedNameFinder.findShortestImportableQName(myElement, vFile);
if (qName != null && imports.contains(qName)) {
rate += 70;
}
}
}
if (myElement.getParent() instanceof PyCallExpression) {
if (target instanceof PyFunction) rate += 50;
}
else {
if (!(target instanceof PyFunction)) rate += 50;
}
return rate;
}
private static boolean canQualifyAnImplicitName(@NotNull PyExpression qualifier, @Nullable PyType qualType) {
if (qualType == null) {
if (qualifier instanceof PyCallExpression) {
PyExpression callee = ((PyCallExpression)qualifier).getCallee();
if (callee instanceof PyReferenceExpression && PyNames.SUPER.equals(callee.getName())) {
PsiElement target = ((PyReferenceExpression)callee).getReference().resolve();
if (target != null && PyBuiltinCache.getInstance(qualifier).isBuiltin(target)) return false; // super() of unresolved type
}
}
}
return true;
}
private static boolean addAssignedAttributes(ResolveResultList ret, String referencedName, PyQualifiedExpression qualifier) {
for (PyExpression ex : collectAssignedAttributes(qualifier)) {
if (referencedName.equals(ex.getName())) {
ret.poke(ex, RatedResolveResult.RATE_NORMAL);
return true;
}
}
return false;
}
private void addDocReference(ResolveResultList ret, PyExpression qualifier, PyType qualifierType) {
PsiElement docstring = null;
if (qualifierType instanceof PyClassType) {
PyClass qualClass = ((PyClassType)qualifierType).getPyClass();
docstring = qualClass.getDocStringExpression();
}
else if (qualifierType instanceof PyModuleType) {
PyFile qualModule = ((PyModuleType)qualifierType).getModule();
docstring = qualModule.getDocStringExpression();
}
else if (qualifier instanceof PyReferenceExpression) {
PsiElement qual_object = ((PyReferenceExpression)qualifier).getReference(myContext).resolve();
if (qual_object instanceof PyDocStringOwner) {
docstring = ((PyDocStringOwner)qual_object).getDocStringExpression();
}
}
ret.poke(docstring, RatedResolveResult.RATE_HIGH);
}
@NotNull
@Override
public Object[] getVariants() {
PyExpression qualifier = myElement.getQualifier();
if (qualifier != null) {
qualifier = CompletionUtil.getOriginalOrSelf(qualifier);
}
if (qualifier == null) {
return EMPTY_ARRAY;
}
final PyQualifiedExpression element = CompletionUtil.getOriginalOrSelf(myElement);
PyType qualifierType = TypeEvalContext.userInitiated(element.getContainingFile()).getType(qualifier);
ProcessingContext ctx = new ProcessingContext();
final Set<String> namesAlready = new HashSet<String>();
ctx.put(PyType.CTX_NAMES, namesAlready);
if (qualifierType != null) {
Collection<Object> variants = new ArrayList<Object>();
Collections.addAll(variants, getVariantFromHasAttr(qualifier));
if (qualifier instanceof PyQualifiedExpression) {
Collection<PyExpression> attrs = collectAssignedAttributes((PyQualifiedExpression)qualifier);
for (PyExpression ex : attrs) {
final String name = ex.getName();
if (name != null && name.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
continue;
}
if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType) {
variants.add(LookupElementBuilder.create((PsiNamedElement)ex)
.withTypeText(qualifierType.getName())
.withIcon(PlatformIcons.FIELD_ICON));
}
if (ex instanceof PyReferenceExpression) {
PyReferenceExpression refExpr = (PyReferenceExpression)ex;
namesAlready.add(refExpr.getReferencedName());
}
else if (ex instanceof PyTargetExpression) {
PyTargetExpression targetExpr = (PyTargetExpression)ex;
namesAlready.add(targetExpr.getName());
}
}
Collections.addAll(variants, qualifierType.getCompletionVariants(element.getName(), element, ctx));
return variants.toArray();
}
else {
return qualifierType.getCompletionVariants(element.getName(), element, ctx);
}
}
return getUntypedVariants();
}
private Object[] getVariantFromHasAttr(PyExpression qualifier) {
Collection<Object> variants = new ArrayList<Object>();
PyIfStatement ifStatement = PsiTreeUtil.getParentOfType(myElement, PyIfStatement.class);
while (ifStatement != null) {
PyExpression condition = ifStatement.getIfPart().getCondition();
if (condition instanceof PyCallExpression && ((PyCallExpression)condition).isCalleeText(PyNames.HAS_ATTR)) {
PyCallExpression call = (PyCallExpression)condition;
if (call.getArguments().length > 1 && call.getArguments()[0].getText().equals(qualifier.getText())) {
PyStringLiteralExpression string = call.getArgument(1, PyStringLiteralExpression.class);
if (string != null && StringUtil.isJavaIdentifier(string.getStringValue())) variants.add(string.getStringValue());
}
}
ifStatement = PsiTreeUtil.getParentOfType(ifStatement, PyIfStatement.class);
}
return variants.toArray();
}
private Object[] getUntypedVariants() {
final PyExpression qualifierElement = myElement.getQualifier();
if (qualifierElement instanceof PyReferenceExpression) {
PyReferenceExpression qualifier = (PyReferenceExpression)qualifierElement;
final String className = qualifier.getReferencedName();
if (className != null) {
Collection<PyClass> classes = PyClassNameIndexInsensitive.find(className, getElement().getProject());
classes = filterByImports(classes, myElement.getContainingFile());
if (classes.size() == 1) {
final PyClassType classType = new PyClassTypeImpl(classes.iterator().next(), false);
return getTypeCompletionVariants(myElement, classType);
}
}
return collectSeenMembers(qualifier.getText());
}
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
private static Collection<PyClass> filterByImports(Collection<PyClass> classes, PsiFile containingFile) {
if (classes.size() <= 1) {
return classes;
}
List<PyClass> result = new ArrayList<PyClass>();
for (PyClass pyClass : classes) {
if (pyClass.getContainingFile() == containingFile) {
result.add(pyClass);
}
else {
final PsiElement exportedClass = ((PyFile)containingFile).getElementNamed(pyClass.getName());
if (exportedClass == pyClass) {
result.add(pyClass);
}
}
}
return result;
}
private Object[] collectSeenMembers(final String text) {
final Set<String> members = new HashSet<String>();
myElement.getContainingFile().accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
super.visitPyReferenceExpression(node);
if (node != myElement) {
final PyExpression qualifier = node.getQualifier();
if (qualifier != null && qualifier.getText().equals(text)) {
final String refName = node.getReferencedName();
if (refName != null) {
members.add(refName);
}
}
}
}
});
List<LookupElement> results = new ArrayList<LookupElement>(members.size());
for (String member : members) {
results.add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(LookupElementBuilder.create(member)));
}
return results.toArray(new Object[results.size()]);
}
private static Collection<PyExpression> collectAssignedAttributes(PyQualifiedExpression qualifier) {
final Set<String> names = new HashSet<String>();
final QualifiedName qualifierQName = qualifier.asQualifiedName();
if (qualifierQName != null) {
final List<PyExpression> results = new ArrayList<PyExpression>();
for (ScopeOwner owner = ScopeUtil.getScopeOwner(qualifier); owner != null; owner = ScopeUtil.getScopeOwner(owner)) {
final Scope scope = ControlFlowCache.getScope(owner);
for (PyTargetExpression target : scope.getTargetExpressions()) {
final QualifiedName targetQName = target.asQualifiedName();
if (targetQName != null) {
if (targetQName.getComponentCount() == qualifierQName.getComponentCount() + 1 && targetQName.matchesPrefix(qualifierQName)) {
final String name = target.getName();
if (!names.contains(name)) {
names.add(name);
results.add(target);
}
}
}
}
}
return results;
}
return Collections.emptyList();
}
@Override
public boolean isReferenceTo(PsiElement element) {
// performance: a qualified reference can never resolve to a local variable or parameter
if (isLocalScope(element)) {
return false;
}
final String referencedName = myElement.getReferencedName();
PyResolveContext resolveContext = myContext.withoutImplicits();
// Guess type eval context origin for switching to local dataflow and return type analysis
if (resolveContext.getTypeEvalContext().getOrigin() == null) {
final PsiFile containingFile = myElement.getContainingFile();
if (containingFile instanceof StubBasedPsiElement) {
assert ((StubBasedPsiElement)containingFile).getStub() == null : "Stub origin for type eval context in isReferenceTo()";
}
final TypeEvalContext context = TypeEvalContext.codeAnalysis(containingFile);
resolveContext = resolveContext.withTypeEvalContext(context);
}
if (element instanceof PyFunction && Comparing.equal(referencedName, ((PyFunction)element).getName()) &&
((PyFunction)element).getContainingClass() != null && !PyNames.INIT.equals(referencedName)) {
final PyExpression qualifier = myElement.getQualifier();
if (qualifier != null) {
final PyType qualifierType = resolveContext.getTypeEvalContext().getType(qualifier);
if (qualifierType == null) {
return true;
}
}
}
for (ResolveResult result : copyWithResolveContext(resolveContext).multiResolve(false)) {
LOG.assertTrue(!(result instanceof ImplicitResolveResult));
PsiElement resolveResult = result.getElement();
if (isResolvedToResult(element, resolveResult)) {
return true;
}
}
return false;
}
@NotNull
protected PyQualifiedReference copyWithResolveContext(PyResolveContext context) {
return new PyQualifiedReference(myElement, context);
}
private boolean isResolvedToResult(PsiElement element, PsiElement resolveResult) {
if (resolveResult instanceof PyImportedModule) {
resolveResult = resolveResult.getNavigationElement();
}
if (element instanceof PsiDirectory && resolveResult instanceof PyFile &&
PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) {
return true;
}
if (resolveResult == element) {
return true;
}
if (resolveResult instanceof PyTargetExpression && PyUtil.isAttribute((PyTargetExpression)resolveResult) &&
element instanceof PyTargetExpression && PyUtil.isAttribute((PyTargetExpression)element) && Comparing.equal(
((PyTargetExpression)resolveResult).getReferencedName(),
((PyTargetExpression)element).getReferencedName())) {
PyClass aClass = PsiTreeUtil.getParentOfType(resolveResult, PyClass.class);
PyClass bClass = PsiTreeUtil.getParentOfType(element, PyClass.class);
if (isSubclass(aClass, bClass)
|| (isSubclass(bClass, aClass))) {
return true;
}
}
if (resolvesToWrapper(element, resolveResult)) {
return true;
}
return false;
}
private static boolean isSubclass(@Nullable PyClass aClass, @Nullable PyClass bClass) {
if (aClass == null || bClass == null) {
return false;
}
return bClass.isSubclass(aClass);
}
private static boolean isLocalScope(PsiElement element) {
if (element instanceof PyParameter) {
return true;
}
if (element instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)element;
return !target.isQualified() && ScopeUtil.getScopeOwner(target) instanceof PyFunction;
}
return false;
}
@Override
public String toString() {
return "PyQualifiedReference(" + myElement + "," + myContext + ")";
}
}