blob: 126ed26acf32e0bdc58631c55bc9358532945b7e [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;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionException;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.console.PydevConsoleRunner;
import com.jetbrains.python.console.completion.PydevConsoleReference;
import com.jetbrains.python.console.pydev.ConsoleCommunication;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.references.PyImportReference;
import com.jetbrains.python.psi.impl.references.PyQualifiedReference;
import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.refactoring.PyDefUseUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Implements reference expression PSI.
*
* @author yole
*/
public class PyReferenceExpressionImpl extends PyElementImpl implements PyReferenceExpression {
private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.psi.impl.PyReferenceExpressionImpl");
private QualifiedName myQualifiedName = null;
public PyReferenceExpressionImpl(ASTNode astNode) {
super(astNode);
}
@NotNull
@Override
public PsiPolyVariantReference getReference() {
return getReference(PyResolveContext.defaultContext());
}
@NotNull
@Override
public PsiPolyVariantReference getReference(PyResolveContext context) {
final PsiFile file = getContainingFile();
final PyExpression qualifier = getQualifier();
// Handle import reference
final PsiElement importParent = PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class);
if (importParent != null) {
return PyImportReference.forElement(this, importParent, context);
}
// Return special reference
final ConsoleCommunication communication = file.getCopyableUserData(PydevConsoleRunner.CONSOLE_KEY);
if (communication != null) {
if (qualifier != null) {
return new PydevConsoleReference(this, communication, qualifier.getText() + ".");
}
return new PydevConsoleReference(this, communication, "");
}
if (qualifier != null) {
return new PyQualifiedReference(this, context);
}
return new PyReferenceImpl(this, context);
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyReferenceExpression(this);
}
@Nullable
public PyExpression getQualifier() {
final ASTNode[] nodes = getNode().getChildren(PythonDialectsTokenSetProvider.INSTANCE.getExpressionTokens());
return (PyExpression)(nodes.length == 1 ? nodes[0].getPsi() : null);
}
@Override
public boolean isQualified() {
return getQualifier() != null;
}
@Nullable
public String getReferencedName() {
final ASTNode nameElement = getNameElement();
return nameElement != null ? nameElement.getText() : null;
}
@Nullable
public ASTNode getNameElement() {
return getNode().findChildByType(PyTokenTypes.IDENTIFIER);
}
@Nullable
@Override
public String getName() {
return getReferencedName();
}
private final QualifiedResolveResult EMPTY_RESULT = new QualifiedResolveResultEmpty();
@NotNull
public QualifiedResolveResult followAssignmentsChain(PyResolveContext resolveContext) {
PyReferenceExpression seeker = this;
QualifiedResolveResult ret = null;
List<PyExpression> qualifiers = new ArrayList<PyExpression>();
PyExpression qualifier = seeker.getQualifier();
if (qualifier != null) {
qualifiers.add(qualifier);
}
Set<PsiElement> visited = new HashSet<PsiElement>();
visited.add(this);
SEARCH:
while (ret == null) {
ResolveResult[] targets = seeker.getReference(resolveContext).multiResolve(false);
for (ResolveResult target : targets) {
PsiElement elt = target.getElement();
if (elt instanceof PyTargetExpression) {
PsiElement assigned_from = null;
final PyTargetExpression expr = (PyTargetExpression)elt;
final TypeEvalContext context = resolveContext.getTypeEvalContext();
if (context.maySwitchToAST(expr) || expr.getStub() == null) {
assigned_from = expr.findAssignedValue();
}
// TODO: Maybe findAssignedValueByStub() should become a part of the PyTargetExpression interface
else if (elt instanceof PyTargetExpressionImpl) {
assigned_from = ((PyTargetExpressionImpl)elt).findAssignedValueByStub(context);
}
if (assigned_from instanceof PyReferenceExpression) {
if (visited.contains(assigned_from)) {
break;
}
visited.add(assigned_from);
seeker = (PyReferenceExpression)assigned_from;
if (seeker.getQualifier() != null) {
qualifiers.add(seeker.getQualifier());
}
continue SEARCH;
}
else if (assigned_from != null) ret = new QualifiedResolveResultImpl(assigned_from, qualifiers, false);
}
else if (ret == null && elt instanceof PyElement && target.isValidResult()) {
// remember this result, but a further reference may be the next resolve result
ret = new QualifiedResolveResultImpl(elt, qualifiers, target instanceof ImplicitResolveResult);
}
}
// all resolve results checked, reassignment not detected, nothing more to do
break;
}
if (ret == null) ret = EMPTY_RESULT;
return ret;
}
@Nullable
public QualifiedName asQualifiedName() {
if (myQualifiedName == null) {
myQualifiedName = PyPsiUtils.asQualifiedName(this);
}
return myQualifiedName;
}
@Override
public String toString() {
return "PyReferenceExpression: " + getReferencedName();
}
public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
if (!TypeEvalStack.mayEvaluate(this)) {
return null;
}
try {
final boolean qualified = isQualified();
if (!qualified) {
String name = getReferencedName();
if (PyNames.NONE.equals(name)) {
return PyNoneType.INSTANCE;
}
}
PyType type = getTypeFromProviders(context);
if (type != null) {
return type;
}
if (qualified) {
PyType maybe_type = PyUtil.getSpecialAttributeType(this, context);
if (maybe_type != null) return maybe_type;
Ref<PyType> typeOfProperty = getTypeOfProperty(context);
if (typeOfProperty != null) {
return typeOfProperty.get();
}
}
ResolveResult[] targets = getReference(PyResolveContext.noImplicits().withTypeEvalContext(context)).multiResolve(false);
if (targets.length == 0) {
return getQualifiedReferenceTypeByControlFlow(context);
}
for (ResolveResult resolveResult : targets) {
PsiElement target = resolveResult.getElement();
if (target == this || target == null) {
continue;
}
if (!target.isValid()) {
LOG.error("Reference " + this + " resolved to invalid element " + target + " (text=" + target.getText() + ")");
continue;
}
type = getTypeFromTarget(target, context, this);
if (type != null) {
return type;
}
}
return null;
}
finally {
TypeEvalStack.evaluated(this);
}
}
@Nullable
public PyType getQualifiedReferenceTypeByControlFlow(@NotNull TypeEvalContext context) {
PyExpression qualifier = getQualifier();
if (context.allowDataFlow(this) && qualifier != null) {
PyExpression next = qualifier;
while (next != null) {
qualifier = next;
next = qualifier instanceof PyQualifiedExpression ? ((PyQualifiedExpression)qualifier).getQualifier() : null;
}
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(this);
final QualifiedName qname = asQualifiedName();
if (qname != null && scopeOwner != null) {
return getTypeByControlFlow(qname.toString(), context, qualifier, scopeOwner);
}
}
return null;
}
@Nullable
public Ref<PyType> getTypeOfProperty(@NotNull TypeEvalContext context) {
final PyExpression qualifier = getQualifier();
final String name = getName();
if (name != null && qualifier != null) {
final PyType qualifierType = context.getType(qualifier);
return getTypeOfProperty(qualifierType, name, context);
}
return null;
}
@Nullable
private Ref<PyType> getTypeOfProperty(@Nullable PyType qualifierType, @NotNull String name, @NotNull TypeEvalContext context) {
if (qualifierType instanceof PyClassType) {
final PyClassType classType = (PyClassType)qualifierType;
PyClass pyClass = classType.getPyClass();
Property property = pyClass.findProperty(name, true);
if (property != null) {
if (classType.isDefinition()) {
return Ref.<PyType>create(PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY));
}
if (AccessDirection.of(this) == AccessDirection.READ) {
final PyType type = property.getType(context);
if (type != null) {
return Ref.create(type);
}
}
return Ref.create();
}
}
else if (qualifierType instanceof PyUnionType) {
final PyUnionType unionType = (PyUnionType)qualifierType;
for (PyType type : unionType.getMembers()) {
final Ref<PyType> result = getTypeOfProperty(type, name, context);
if (result != null) {
return result;
}
}
}
return null;
}
@Nullable
private PyType getTypeFromProviders(@NotNull TypeEvalContext context) {
for (PyTypeProvider provider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
try {
final PyType type = provider.getReferenceExpressionType(this, context);
if (type != null) {
return type;
}
}
catch (AbstractMethodError e) {
LOG.info(new ExtensionException(provider.getClass()));
}
}
return null;
}
@Nullable
public static PyType getTypeFromTarget(@NotNull final PsiElement target,
final TypeEvalContext context,
PyReferenceExpression anchor) {
if (!(target instanceof PyTargetExpression)) { // PyTargetExpression will ask about its type itself
final PyType pyType = getReferenceTypeFromProviders(target, context, anchor);
if (pyType != null) {
return pyType;
}
}
if (target instanceof PyTargetExpression) {
final String name = ((PyTargetExpression)target).getName();
if (PyNames.NONE.equals(name)) {
return PyNoneType.INSTANCE;
}
if (PyNames.TRUE.equals(name) || PyNames.FALSE.equals(name)) {
return PyBuiltinCache.getInstance(target).getBoolType();
}
}
if (target instanceof PyFile) {
return new PyModuleType((PyFile)target);
}
if (target instanceof PyImportedModule) {
return new PyImportedModuleType((PyImportedModule)target);
}
if ((target instanceof PyTargetExpression || target instanceof PyNamedParameter) && anchor != null && context.allowDataFlow(anchor)) {
final ScopeOwner scopeOwner = PsiTreeUtil.getStubOrPsiParentOfType(anchor, ScopeOwner.class);
if (scopeOwner != null && scopeOwner == PsiTreeUtil.getStubOrPsiParentOfType(target, ScopeOwner.class)) {
final String name = ((PyElement)target).getName();
if (name != null) {
final PyType type = getTypeByControlFlow(name, context, anchor, scopeOwner);
if (type != null) {
return type;
}
}
}
}
if (target instanceof PyFunction) {
final PyDecoratorList decoratorList = ((PyFunction)target).getDecoratorList();
if (decoratorList != null) {
final PyDecorator propertyDecorator = decoratorList.findDecorator(PyNames.PROPERTY);
if (propertyDecorator != null) {
return PyBuiltinCache.getInstance(target).getObjectType(PyNames.PROPERTY);
}
for (PyDecorator decorator : decoratorList.getDecorators()) {
final QualifiedName qName = decorator.getQualifiedName();
if (qName != null && (qName.endsWith(PyNames.SETTER) || qName.endsWith(PyNames.DELETER) ||
qName.endsWith(PyNames.GETTER))) {
return PyBuiltinCache.getInstance(target).getObjectType(PyNames.PROPERTY);
}
}
}
}
if (target instanceof PyTypedElement) {
return context.getType((PyTypedElement)target);
}
if (target instanceof PsiDirectory) {
PsiFile file = ((PsiDirectory)target).findFile(PyNames.INIT_DOT_PY);
if (file != null) {
return getTypeFromTarget(file, context, anchor);
}
}
return null;
}
private static PyType getTypeByControlFlow(@NotNull String name,
@NotNull TypeEvalContext context,
@NotNull PyExpression anchor,
@NotNull ScopeOwner scopeOwner) {
PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(anchor, PyAugAssignmentStatement.class);
try {
final PyElement element = augAssignment != null ? augAssignment : anchor;
final List<ReadWriteInstruction> defs = PyDefUseUtil.getLatestDefs(scopeOwner, name, element, true);
if (!defs.isEmpty()) {
PyType type = defs.get(0).getType(context, anchor);
for (int i = 1; i < defs.size(); i++) {
type = PyUnionType.union(type, defs.get(i).getType(context, anchor));
}
return type;
}
}
catch (PyDefUseUtil.InstructionNotFoundException ignored) {
}
return null;
}
@Nullable
public static PyType getReferenceTypeFromProviders(@NotNull final PsiElement target,
TypeEvalContext context,
@Nullable PsiElement anchor) {
for (PyTypeProvider provider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
final PyType result = provider.getReferenceType(target, context, anchor);
if (result != null) return result;
}
return null;
}
@Override
public void subtreeChanged() {
super.subtreeChanged();
myQualifiedName = null;
}
private static class QualifiedResolveResultImpl extends RatedResolveResult implements QualifiedResolveResult {
// a trivial implementation
private List<PyExpression> myQualifiers;
private boolean myIsImplicit;
public boolean isImplicit() {
return myIsImplicit;
}
QualifiedResolveResultImpl(@NotNull PsiElement element, List<PyExpression> qualifiers, boolean isImplicit) {
super(isImplicit ? RATE_LOW : RATE_NORMAL, element);
myQualifiers = qualifiers;
myIsImplicit = isImplicit;
}
@Override
public List<PyExpression> getQualifiers() {
return myQualifiers;
}
}
public static class QualifiedResolveResultEmpty implements QualifiedResolveResult {
// a trivial implementation
public QualifiedResolveResultEmpty() {
}
@Override
public List<PyExpression> getQualifiers() {
return Collections.emptyList();
}
public PsiElement getElement() {
return null;
}
public boolean isValidResult() {
return false;
}
public boolean isImplicit() {
return false;
}
}
}