blob: 611cbc84ad2ed1ab9a7775c00c0d5c070d9cbc87 [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.codeInsight.completion.CompletionUtil;
import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.*;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.documentation.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.stubs.PropertyStubStorage;
import com.jetbrains.python.psi.stubs.PyClassStub;
import com.jetbrains.python.psi.stubs.PyFunctionStub;
import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.toolbox.Maybe;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import static com.intellij.openapi.util.text.StringUtil.join;
import static com.intellij.openapi.util.text.StringUtil.notNullize;
/**
* @author yole
*/
public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyClass {
public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
private List<PyTargetExpression> myInstanceAttributes;
private final NotNullLazyValue<CachedValue<Boolean>> myNewStyle = new NotNullLazyValue<CachedValue<Boolean>>() {
@NotNull
@Override
protected CachedValue<Boolean> compute() {
return CachedValuesManager.getManager(getProject()).createCachedValue(new NewStyleCachedValueProvider(), false);
}
};
private volatile Map<String, Property> myPropertyCache;
private class CachedAncestorsProvider implements ParameterizedCachedValueProvider<List<PyClassLikeType>, TypeEvalContext> {
@Nullable
@Override
public CachedValueProvider.Result<List<PyClassLikeType>> compute(@NotNull TypeEvalContext context) {
final List<PyClassLikeType> ancestorTypes = isNewStyleClass() ? getMROAncestorTypes(context) : getOldStyleAncestorTypes(context);
return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}
private final Key<ParameterizedCachedValue<List<PyClassLikeType>, TypeEvalContext>> myCachedValueKey = Key.create("cached ancestors");
private final CachedAncestorsProvider myCachedAncestorsProvider = new CachedAncestorsProvider();
@Override
public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
return new PyClassTypeImpl(this, true);
}
private class NewStyleCachedValueProvider implements CachedValueProvider<Boolean> {
@Override
public Result<Boolean> compute() {
return new Result<Boolean>(calculateNewStyleClass(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}
public PyClassImpl(@NotNull ASTNode astNode) {
super(astNode);
}
public PyClassImpl(@NotNull final PyClassStub stub) {
this(stub, PyElementTypes.CLASS_DECLARATION);
}
public PyClassImpl(@NotNull final PyClassStub stub, @NotNull IStubElementType nodeType) {
super(stub, nodeType);
}
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
final ASTNode nameElement = PyUtil.createNewName(this, name);
final ASTNode node = getNameNode();
if (node != null) {
getNode().replaceChild(node, nameElement);
}
return this;
}
@Nullable
@Override
public String getName() {
final PyClassStub stub = getStub();
if (stub != null) {
return stub.getName();
}
else {
ASTNode node = getNameNode();
return node != null ? node.getText() : null;
}
}
public PsiElement getNameIdentifier() {
final ASTNode nameNode = getNameNode();
return nameNode != null ? nameNode.getPsi() : null;
}
public ASTNode getNameNode() {
return getNode().findChildByType(PyTokenTypes.IDENTIFIER);
}
@Override
public Icon getIcon(int flags) {
return PlatformIcons.CLASS_ICON;
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyClass(this);
}
@Override
@NotNull
public PyStatementList getStatementList() {
final PyStatementList statementList = childToPsi(PyElementTypes.STATEMENT_LIST);
assert statementList != null : "Statement list missing for class " + getText();
return statementList;
}
@Override
public PyArgumentList getSuperClassExpressionList() {
final PyArgumentList argList = PsiTreeUtil.getChildOfType(this, PyArgumentList.class);
if (argList != null && argList.getFirstChild() != null) {
return argList;
}
return null;
}
@NotNull
public PyExpression[] getSuperClassExpressions() {
final PyArgumentList argList = getSuperClassExpressionList();
if (argList != null) {
return argList.getArguments();
}
return PyExpression.EMPTY_ARRAY;
}
@NotNull
public static PyExpression unfoldClass(@NotNull PyExpression expression) {
if (expression instanceof PyCallExpression) {
PyCallExpression call = (PyCallExpression)expression;
final PyExpression callee = call.getCallee();
final PyExpression[] arguments = call.getArguments();
if (callee != null && "with_metaclass".equals(callee.getName()) && arguments.length > 1) {
final PyExpression secondArgument = arguments[1];
if (secondArgument != null) {
return secondArgument;
}
}
}
// Heuristic: unfold Foo[Bar] to Foo for subscription expressions for superclasses
else if (expression instanceof PySubscriptionExpression) {
final PySubscriptionExpression subscriptionExpr = (PySubscriptionExpression)expression;
return subscriptionExpr.getOperand();
}
return expression;
}
@NotNull
@Override
public List<PyClass> getAncestorClasses() {
return getAncestorClasses(TypeEvalContext.codeInsightFallback());
}
@NotNull
@Override
public List<PyClass> getAncestorClasses(@NotNull TypeEvalContext context) {
final List<PyClass> results = new ArrayList<PyClass>();
for (PyClassLikeType type : getAncestorTypes(context)) {
if (type instanceof PyClassType) {
results.add(((PyClassType)type).getPyClass());
}
}
return results;
}
public boolean isSubclass(PyClass parent) {
if (this == parent) {
return true;
}
for (PyClass superclass : getAncestorClasses()) {
if (parent == superclass) return true;
}
return false;
}
@Override
public boolean isSubclass(@NotNull String superClassQName) {
if (superClassQName.equals(getQualifiedName())) {
return true;
}
for (PyClassLikeType type : getAncestorTypes(TypeEvalContext.codeInsightFallback())) {
if (type != null && superClassQName.equals(type.getClassQName())) {
return true;
}
}
return false;
}
public PyDecoratorList getDecoratorList() {
return getStubOrPsiChild(PyElementTypes.DECORATOR_LIST);
}
@Nullable
public String getQualifiedName() {
return QualifiedNameFinder.getQualifiedName(this);
}
@Override
public List<String> getSlots() {
final Set<String> result = new LinkedHashSet<String>();
boolean found = false;
final List<String> ownSlots = getOwnSlots();
if (ownSlots != null) {
found = true;
result.addAll(ownSlots);
}
for (PyClass cls : getAncestorClasses()) {
final List<String> ancestorSlots = cls.getOwnSlots();
if (ancestorSlots != null) {
found = true;
result.addAll(ancestorSlots);
}
}
return found ? new ArrayList<String>(result) : null;
}
@Nullable
@Override
public List<String> getOwnSlots() {
final PyClassStub stub = getStub();
if (stub != null) {
return stub.getSlots();
}
return PyFileImpl.getStringListFromTargetExpression(PyNames.SLOTS, getClassAttributes());
}
@NotNull
public PyClass[] getSuperClasses() {
final List<PyClassLikeType> superTypes = getSuperClassTypes(TypeEvalContext.codeInsightFallback());
if (superTypes.isEmpty()) {
return EMPTY_ARRAY;
}
final List<PyClass> result = new ArrayList<PyClass>();
for (PyClassLikeType type : superTypes) {
if (type instanceof PyClassType) {
result.add(((PyClassType)type).getPyClass());
}
}
return result.toArray(new PyClass[result.size()]);
}
@Override
public ItemPresentation getPresentation() {
return new PyElementPresentation(this) {
@Nullable
@Override
public String getPresentableText() {
if (!isValid()) {
return null;
}
final StringBuilder result = new StringBuilder(notNullize(getName(), PyNames.UNNAMED_ELEMENT));
final PyExpression[] superClassExpressions = getSuperClassExpressions();
if (superClassExpressions.length > 0) {
result.append("(");
result.append(join(Arrays.asList(superClassExpressions), new Function<PyExpression, String>() {
public String fun(PyExpression expr) {
String name = expr.getText();
return notNullize(name, PyNames.UNNAMED_ELEMENT);
}
}, ", "));
result.append(")");
}
return result.toString();
}
};
}
@NotNull
private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) {
List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
while (true) {
// filter blank sequences
List<List<PyClassLikeType>> nonBlankSequences = new ArrayList<List<PyClassLikeType>>(sequences.size());
for (List<PyClassLikeType> item : sequences) {
if (item.size() > 0) nonBlankSequences.add(item);
}
if (nonBlankSequences.isEmpty()) return result;
// find a clean head
boolean found = false;
PyClassLikeType head = null; // to keep compiler happy; really head is assigned in the loop at least once.
for (List<PyClassLikeType> seq : nonBlankSequences) {
head = seq.get(0);
boolean head_in_tails = false;
for (List<PyClassLikeType> tail_seq : nonBlankSequences) {
if (tail_seq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
head_in_tails = true;
break;
}
}
if (!head_in_tails) {
found = true;
break;
}
else {
head = null; // as a signal
}
}
if (!found) {
// Inconsistent hierarchy results in TypeError
throw new IllegalStateException("Inconsistent class hierarchy");
}
// our head is clean;
result.add(head);
// remove it from heads of other sequences
for (List<PyClassLikeType> seq : nonBlankSequences) {
if (Comparing.equal(seq.get(0), head)) {
seq.remove(0);
}
}
} // we either return inside the loop or die by assertion
}
@NotNull
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, @NotNull Set<PyClassLikeType> seen, boolean addThisType,
@NotNull TypeEvalContext context) {
if (seen.contains(type)) {
throw new IllegalStateException("Circular class inheritance");
}
final List<PyClassLikeType> bases = type.getSuperClassTypes(context);
List<List<PyClassLikeType>> lines = new ArrayList<List<PyClassLikeType>>();
for (PyClassLikeType base : bases) {
if (base != null) {
final Set<PyClassLikeType> newSeen = new HashSet<PyClassLikeType>(seen);
newSeen.add(type);
List<PyClassLikeType> lin = mroLinearize(base, newSeen, true, context);
if (!lin.isEmpty()) lines.add(lin);
}
}
if (!bases.isEmpty()) {
lines.add(bases);
}
List<PyClassLikeType> result = mroMerge(lines);
if (addThisType) {
result.add(0, type);
}
return result;
}
@NotNull
public PyFunction[] getMethods() {
return getClassChildren(PythonDialectsTokenSetProvider.INSTANCE.getFunctionDeclarationTokens(), PyFunction.ARRAY_FACTORY);
}
@Override
@NotNull
public Map<String, Property> getProperties() {
initProperties();
return new HashMap<String, Property>(myPropertyCache);
}
@Override
public PyClass[] getNestedClasses() {
return getClassChildren(TokenSet.create(PyElementTypes.CLASS_DECLARATION), PyClass.ARRAY_FACTORY);
}
protected <T extends PsiElement> T[] getClassChildren(TokenSet elementTypes, ArrayFactory<T> factory) {
// TODO: gather all top-level functions, maybe within control statements
final PyClassStub classStub = getStub();
if (classStub != null) {
return classStub.getChildrenByType(elementTypes, factory);
}
List<T> result = new ArrayList<T>();
final PyStatementList statementList = getStatementList();
for (PsiElement element : statementList.getChildren()) {
if (elementTypes.contains(element.getNode().getElementType())) {
//noinspection unchecked
result.add((T)element);
}
}
return result.toArray(factory.create(result.size()));
}
private static class NameFinder<T extends PyElement> implements Processor<T> {
private T myResult;
private final String[] myNames;
public NameFinder(String... names) {
myNames = names;
myResult = null;
}
public T getResult() {
return myResult;
}
public boolean process(T target) {
final String targetName = target.getName();
for (String name : myNames) {
if (name.equals(targetName)) {
myResult = target;
return false;
}
}
return true;
}
}
public PyFunction findMethodByName(@Nullable final String name, boolean inherited) {
if (name == null) return null;
NameFinder<PyFunction> proc = new NameFinder<PyFunction>(name);
visitMethods(proc, inherited);
return proc.getResult();
}
@Nullable
@Override
public PyClass findNestedClass(String name, boolean inherited) {
if (name == null) return null;
NameFinder<PyClass> proc = new NameFinder<PyClass>(name);
visitNestedClasses(proc, inherited);
return proc.getResult();
}
@Nullable
public PyFunction findInitOrNew(boolean inherited) {
NameFinder<PyFunction> proc;
if (isNewStyleClass()) {
proc = new NameFinder<PyFunction>(PyNames.INIT, PyNames.NEW);
}
else {
proc = new NameFinder<PyFunction>(PyNames.INIT);
}
visitMethods(proc, inherited, true);
return proc.getResult();
}
private final static Maybe<Callable> UNKNOWN_CALL = new Maybe<Callable>(); // denotes _not_ a PyFunction, actually
private final static Maybe<Callable> NONE = new Maybe<Callable>(null); // denotes an explicit None
/**
* @param name name of the property
* @param property_filter returns true if the property is acceptable
* @param advanced is @foo.setter syntax allowed
* @return the first property that both filters accepted.
*/
@Nullable
private Property processPropertiesInClass(@Nullable String name, @Nullable Processor<Property> property_filter, boolean advanced) {
// NOTE: fast enough to be rerun every time
Property prop = processDecoratedProperties(name, property_filter, advanced);
if (prop != null) return prop;
if (getStub() != null) {
prop = processStubProperties(name, property_filter);
if (prop != null) return prop;
}
else {
// name = property(...) assignments from PSI
for (PyTargetExpression target : getClassAttributes()) {
if (name == null || name.equals(target.getName())) {
prop = PropertyImpl.fromTarget(target);
if (prop != null) {
if (property_filter == null || property_filter.process(prop)) return prop;
}
}
}
}
return null;
}
@Nullable
private Property processDecoratedProperties(@Nullable String name, @Nullable Processor<Property> filter, boolean useAdvancedSyntax) {
// look at @property decorators
Map<String, List<PyFunction>> grouped = new HashMap<String, List<PyFunction>>();
// group suitable same-named methods, each group defines a property
for (PyFunction method : getMethods()) {
final String methodName = method.getName();
if (name == null || name.equals(methodName)) {
List<PyFunction> bucket = grouped.get(methodName);
if (bucket == null) {
bucket = new SmartList<PyFunction>();
grouped.put(methodName, bucket);
}
bucket.add(method);
}
}
for (Map.Entry<String, List<PyFunction>> entry : grouped.entrySet()) {
Maybe<Callable> getter = NONE;
Maybe<Callable> setter = NONE;
Maybe<Callable> deleter = NONE;
String doc = null;
final String decoratorName = entry.getKey();
for (PyFunction method : entry.getValue()) {
final PyDecoratorList decoratorList = method.getDecoratorList();
if (decoratorList != null) {
for (PyDecorator deco : decoratorList.getDecorators()) {
final QualifiedName qname = deco.getQualifiedName();
if (qname != null) {
String decoName = qname.toString();
for (PyKnownDecoratorProvider provider : PyUtil.KnownDecoratorProviderHolder.KNOWN_DECORATOR_PROVIDERS) {
final String knownName = provider.toKnownDecorator(decoName);
if (knownName != null) {
decoName = knownName;
}
}
if (PyNames.PROPERTY.equals(decoName)) {
getter = new Maybe<Callable>(method);
}
else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.GETTER)) {
getter = new Maybe<Callable>(method);
}
else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.SETTER)) {
setter = new Maybe<Callable>(method);
}
else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.DELETER)) {
deleter = new Maybe<Callable>(method);
}
}
}
}
if (getter != NONE && setter != NONE && deleter != NONE) break; // can't improve
}
if (getter != NONE || setter != NONE || deleter != NONE) {
final PropertyImpl prop = new PropertyImpl(decoratorName, getter, setter, deleter, doc, null);
if (filter == null || filter.process(prop)) return prop;
}
}
return null;
}
private Maybe<Callable> fromPacked(Maybe<String> maybeName) {
if (maybeName.isDefined()) {
final String value = maybeName.value();
if (value == null || PyNames.NONE.equals(value)) {
return NONE;
}
PyFunction method = findMethodByName(value, true);
if (method != null) return new Maybe<Callable>(method);
}
return UNKNOWN_CALL;
}
@Nullable
private Property processStubProperties(@Nullable String name, @Nullable Processor<Property> propertyProcessor) {
final PyClassStub stub = getStub();
if (stub != null) {
for (StubElement subStub : stub.getChildrenStubs()) {
if (subStub.getStubType() == PyElementTypes.TARGET_EXPRESSION) {
final PyTargetExpressionStub targetStub = (PyTargetExpressionStub)subStub;
PropertyStubStorage prop = targetStub.getCustomStub(PropertyStubStorage.class);
if (prop != null && (name == null || name.equals(targetStub.getName()))) {
Maybe<Callable> getter = fromPacked(prop.getGetter());
Maybe<Callable> setter = fromPacked(prop.getSetter());
Maybe<Callable> deleter = fromPacked(prop.getDeleter());
String doc = prop.getDoc();
if (getter != NONE || setter != NONE || deleter != NONE) {
final PropertyImpl property = new PropertyImpl(targetStub.getName(), getter, setter, deleter, doc, targetStub.getPsi());
if (propertyProcessor == null || propertyProcessor.process(property)) return property;
}
}
}
}
}
return null;
}
@Nullable
@Override
public Property findProperty(@NotNull final String name, boolean inherited) {
Property property = findLocalProperty(name);
if (property != null) {
return property;
}
if (findMethodByName(name, false) != null || findClassAttribute(name, false) != null) {
return null;
}
if (inherited) {
for (PyClass aClass : getAncestorClasses()) {
final Property ancestorProperty = ((PyClassImpl)aClass).findLocalProperty(name);
if (ancestorProperty != null) {
return ancestorProperty;
}
}
}
return null;
}
@Override
public Property findPropertyByCallable(Callable callable) {
initProperties();
for (Property property : myPropertyCache.values()) {
if (property.getGetter().valueOrNull() == callable ||
property.getSetter().valueOrNull() == callable ||
property.getDeleter().valueOrNull() == callable) {
return property;
}
}
return null;
}
private Property findLocalProperty(String name) {
initProperties();
return myPropertyCache.get(name);
}
private synchronized void initProperties() {
if (myPropertyCache == null) {
myPropertyCache = initializePropertyCache();
}
}
private Map<String, Property> initializePropertyCache() {
final Map<String, Property> result = new HashMap<String, Property>();
processProperties(null, new Processor<Property>() {
@Override
public boolean process(Property property) {
result.put(property.getName(), property);
return false;
}
}, false);
return result;
}
@Nullable
@Override
public Property scanProperties(@Nullable Processor<Property> filter, boolean inherited) {
return processProperties(null, filter, inherited);
}
@Nullable
private Property processProperties(@Nullable String name, @Nullable Processor<Property> filter, boolean inherited) {
if (!isValid()) {
return null;
}
LanguageLevel level = LanguageLevel.getDefault();
// EA-32381: A tree-based instance may not have a parent element somehow, so getContainingFile() may be not appropriate
final PsiFile file = getParentByStub() != null ? getContainingFile() : null;
if (file != null) {
level = LanguageLevel.forElement(file);
}
final boolean useAdvancedSyntax = level.isAtLeast(LanguageLevel.PYTHON26);
final Property local = processPropertiesInClass(name, filter, useAdvancedSyntax);
if (local != null) {
return local;
}
if (inherited) {
if (name != null && (findMethodByName(name, false) != null || findClassAttribute(name, false) != null)) {
return null;
}
for (PyClass cls : getAncestorClasses()) {
final Property property = ((PyClassImpl)cls).processPropertiesInClass(name, filter, useAdvancedSyntax);
if (property != null) {
return property;
}
}
}
return null;
}
private static class PropertyImpl extends PropertyBunch<Callable> implements Property {
private final String myName;
private PropertyImpl(String name,
Maybe<Callable> getter,
Maybe<Callable> setter,
Maybe<Callable> deleter,
String doc,
PyTargetExpression site) {
myName = name;
myDeleter = deleter;
myGetter = getter;
mySetter = setter;
myDoc = doc;
mySite = site;
}
@NotNull
@Override
public Maybe<Callable> getGetter() {
return filterNonStubExpression(myGetter);
}
@NotNull
@Override
public Maybe<Callable> getSetter() {
return filterNonStubExpression(mySetter);
}
@NotNull
@Override
public Maybe<Callable> getDeleter() {
return filterNonStubExpression(myDeleter);
}
public String getName() {
return myName;
}
public PyTargetExpression getDefinitionSite() {
return mySite;
}
@NotNull
@Override
public Maybe<Callable> getByDirection(@NotNull AccessDirection direction) {
switch (direction) {
case READ:
return getGetter();
case WRITE:
return getSetter();
case DELETE:
return getDeleter();
}
throw new IllegalArgumentException("Unknown direction " + PyUtil.nvl(direction));
}
@Nullable
@Override
public PyType getType(@NotNull TypeEvalContext context) {
if (mySite instanceof PyTargetExpressionImpl) {
final PyType targetDocStringType = ((PyTargetExpressionImpl)mySite).getTypeFromDocString();
if (targetDocStringType != null) {
return targetDocStringType;
}
}
final Callable callable = myGetter.valueOrNull();
if (callable != null) {
// Ignore return types of non stub-based elements if we are not allowed to use AST
if (!(callable instanceof StubBasedPsiElement) && !context.maySwitchToAST(callable)) {
return null;
}
return context.getReturnType(callable);
}
return null;
}
@NotNull
@Override
protected Maybe<Callable> translate(@Nullable PyExpression expr) {
if (expr == null) {
return NONE;
}
if (PyNames.NONE.equals(expr.getName())) return NONE; // short-circuit a common case
if (expr instanceof Callable) {
return new Maybe<Callable>((Callable)expr);
}
final PsiReference ref = expr.getReference();
if (ref != null) {
PsiElement something = ref.resolve();
if (something instanceof Callable) {
return new Maybe<Callable>((Callable)something);
}
}
return NONE;
}
@NotNull
private static Maybe<Callable> filterNonStubExpression(@NotNull Maybe<Callable> maybeCallable) {
final Callable callable = maybeCallable.valueOrNull();
if (callable != null) {
if (!(callable instanceof StubBasedPsiElement)) {
return UNKNOWN_CALL;
}
}
return maybeCallable;
}
public String toString() {
return "property(" + myGetter + ", " + mySetter + ", " + myDeleter + ", " + myDoc + ")";
}
@Nullable
public static PropertyImpl fromTarget(PyTargetExpression target) {
PyExpression expr = target.findAssignedValue();
final PropertyImpl prop = new PropertyImpl(target.getName(), null, null, null, null, target);
final boolean success = fillFromCall(expr, prop);
return success ? prop : null;
}
}
public boolean visitMethods(Processor<PyFunction> processor, boolean inherited) {
return visitMethods(processor, inherited, false);
}
public boolean visitMethods(Processor<PyFunction> processor,
boolean inherited,
boolean skipClassObj) {
PyFunction[] methods = getMethods();
if (!ContainerUtil.process(methods, processor)) return false;
if (inherited) {
for (PyClass ancestor : getAncestorClasses()) {
if (skipClassObj && PyNames.FAKE_OLD_BASE.equals(ancestor.getName())) {
continue;
}
if (!ancestor.visitMethods(processor, false)) {
return false;
}
}
}
return true;
}
public boolean visitNestedClasses(Processor<PyClass> processor, boolean inherited) {
PyClass[] nestedClasses = getNestedClasses();
if (!ContainerUtil.process(nestedClasses, processor)) return false;
if (inherited) {
for (PyClass ancestor : getAncestorClasses()) {
if (!((PyClassImpl)ancestor).visitNestedClasses(processor, false)) {
return false;
}
}
}
return true;
}
public boolean visitClassAttributes(Processor<PyTargetExpression> processor, boolean inherited) {
List<PyTargetExpression> methods = getClassAttributes();
if (!ContainerUtil.process(methods, processor)) return false;
if (inherited) {
for (PyClass ancestor : getAncestorClasses()) {
if (!ancestor.visitClassAttributes(processor, false)) {
return false;
}
}
}
return true;
// NOTE: sorry, not enough metaprogramming to generalize visitMethods and visitClassAttributes
}
public List<PyTargetExpression> getClassAttributes() {
PyClassStub stub = getStub();
if (stub != null) {
final PyTargetExpression[] children = stub.getChildrenByType(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.EMPTY_ARRAY);
return Arrays.asList(children);
}
List<PyTargetExpression> result = new ArrayList<PyTargetExpression>();
for (PsiElement psiElement : getStatementList().getChildren()) {
if (psiElement instanceof PyAssignmentStatement) {
final PyAssignmentStatement assignmentStatement = (PyAssignmentStatement)psiElement;
final PyExpression[] targets = assignmentStatement.getTargets();
for (PyExpression target : targets) {
if (target instanceof PyTargetExpression) {
result.add((PyTargetExpression)target);
}
}
}
}
return result;
}
@Override
public PyTargetExpression findClassAttribute(@NotNull String name, boolean inherited) {
final NameFinder<PyTargetExpression> processor = new NameFinder<PyTargetExpression>(name);
visitClassAttributes(processor, inherited);
return processor.getResult();
}
public List<PyTargetExpression> getInstanceAttributes() {
if (myInstanceAttributes == null) {
myInstanceAttributes = collectInstanceAttributes();
}
return myInstanceAttributes;
}
@Nullable
@Override
public PyTargetExpression findInstanceAttribute(String name, boolean inherited) {
final List<PyTargetExpression> instanceAttributes = getInstanceAttributes();
for (PyTargetExpression instanceAttribute : instanceAttributes) {
if (name.equals(instanceAttribute.getReferencedName())) {
return instanceAttribute;
}
}
if (inherited) {
for (PyClass ancestor : getAncestorClasses()) {
final PyTargetExpression attribute = ancestor.findInstanceAttribute(name, false);
if (attribute != null) {
return attribute;
}
}
}
return null;
}
private List<PyTargetExpression> collectInstanceAttributes() {
Map<String, PyTargetExpression> result = new HashMap<String, PyTargetExpression>();
collectAttributesInNew(result);
PyFunctionImpl initMethod = (PyFunctionImpl)findMethodByName(PyNames.INIT, false);
if (initMethod != null) {
collectInstanceAttributes(initMethod, result);
}
Set<String> namesInInit = new HashSet<String>(result.keySet());
final PyFunction[] methods = getMethods();
for (PyFunction method : methods) {
if (!PyNames.INIT.equals(method.getName())) {
collectInstanceAttributes(method, result, namesInInit);
}
}
final Collection<PyTargetExpression> expressions = result.values();
return new ArrayList<PyTargetExpression>(expressions);
}
private void collectAttributesInNew(@NotNull final Map<String, PyTargetExpression> result) {
final PyFunction newMethod = findMethodByName(PyNames.NEW, false);
if (newMethod != null) {
for (PyTargetExpression target : getTargetExpressions(newMethod)) {
result.put(target.getName(), target);
}
}
}
public static void collectInstanceAttributes(@NotNull PyFunction method, @NotNull final Map<String, PyTargetExpression> result) {
collectInstanceAttributes(method, result, null);
}
public static void collectInstanceAttributes(@NotNull PyFunction method,
@NotNull final Map<String, PyTargetExpression> result,
Set<String> existing) {
final PyParameter[] params = method.getParameterList().getParameters();
if (params.length == 0) {
return;
}
for (PyTargetExpression target : getTargetExpressions(method)) {
if (PyUtil.isInstanceAttribute(target) && (existing == null || !existing.contains(target.getName()))) {
result.put(target.getName(), target);
}
}
}
@NotNull
private static List<PyTargetExpression> getTargetExpressions(@NotNull PyFunction function) {
final PyFunctionStub stub = function.getStub();
if (stub != null) {
return Arrays.asList(stub.getChildrenByType(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.EMPTY_ARRAY));
}
else {
final PyStatementList statementList = function.getStatementList();
final List<PyTargetExpression> result = new ArrayList<PyTargetExpression>();
statementList.accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyClass(PyClass node) {}
public void visitPyAssignmentStatement(final PyAssignmentStatement node) {
for (PyExpression expression : node.getTargets()) {
if (expression instanceof PyTargetExpression) {
result.add((PyTargetExpression)expression);
}
}
}
});
return result;
}
}
public boolean isNewStyleClass() {
return myNewStyle.getValue().getValue();
}
private boolean calculateNewStyleClass() {
final PsiFile containingFile = getContainingFile();
if (containingFile instanceof PyFile && ((PyFile)containingFile).getLanguageLevel().isPy3K()) {
return true;
}
final PyClass objClass = PyBuiltinCache.getInstance(this).getClass("object");
if (this == objClass) return true; // a rare but possible case
if (hasNewStyleMetaClass(this)) return true;
for (PyClassLikeType type : getOldStyleAncestorTypes(TypeEvalContext.codeInsightFallback())) {
if (type == null) {
// unknown, assume new-style class
return true;
}
if (type instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)type).getPyClass();
if (pyClass == objClass) return true;
if (hasNewStyleMetaClass(pyClass)) {
return true;
}
}
}
return false;
}
private static boolean hasNewStyleMetaClass(PyClass pyClass) {
final PsiFile containingFile = pyClass.getContainingFile();
if (containingFile instanceof PyFile) {
final PsiElement element = ((PyFile)containingFile).getElementNamed(PyNames.DUNDER_METACLASS);
if (element instanceof PyTargetExpression) {
final QualifiedName qName = ((PyTargetExpression)element).getAssignedQName();
if (qName != null && qName.matches("type")) {
return true;
}
}
}
if (pyClass.findClassAttribute(PyNames.DUNDER_METACLASS, false) != null) {
return true;
}
return false;
}
@Override
public boolean processClassLevelDeclarations(@NotNull PsiScopeProcessor processor) {
final PyClassStub stub = getStub();
if (stub != null) {
final List<StubElement> children = stub.getChildrenStubs();
for (StubElement child : children) {
if (!processor.execute(child.getPsi(), ResolveState.initial())) {
return false;
}
}
}
else {
PyResolveUtil.scopeCrawlUp(processor, this, null, this);
}
return true;
}
@Override
public boolean processInstanceLevelDeclarations(@NotNull PsiScopeProcessor processor, @Nullable PsiElement location) {
Map<String, PyTargetExpression> declarationsInMethod = new HashMap<String, PyTargetExpression>();
PyFunction instanceMethod = PsiTreeUtil.getParentOfType(location, PyFunction.class);
final PyClass containingClass = instanceMethod != null ? instanceMethod.getContainingClass() : null;
if (instanceMethod != null && containingClass != null && CompletionUtil.getOriginalElement(containingClass) == this) {
collectInstanceAttributes(instanceMethod, declarationsInMethod);
for (PyTargetExpression targetExpression : declarationsInMethod.values()) {
if (!processor.execute(targetExpression, ResolveState.initial())) {
return false;
}
}
}
for (PyTargetExpression expr : getInstanceAttributes()) {
if (declarationsInMethod.containsKey(expr.getName())) {
continue;
}
if (!processor.execute(expr, ResolveState.initial())) return false;
}
return true;
}
public int getTextOffset() {
final ASTNode name = getNameNode();
return name != null ? name.getStartOffset() : super.getTextOffset();
}
public PyStringLiteralExpression getDocStringExpression() {
return DocStringUtil.findDocStringExpression(getStatementList());
}
@Override
public String getDocStringValue() {
final PyClassStub stub = getStub();
if (stub != null) {
return stub.getDocString();
}
return DocStringUtil.getDocStringValue(this);
}
@Nullable
@Override
public StructuredDocString getStructuredDocString() {
return DocStringUtil.getStructuredDocString(this);
}
public String toString() {
return "PyClass: " + getName();
}
@NotNull
public Iterable<PyElement> iterateNames() {
return Collections.<PyElement>singleton(this);
}
public PyElement getElementNamed(final String the_name) {
return the_name.equals(getName()) ? this : null;
}
public boolean mustResolveOutside() {
return false;
}
public void subtreeChanged() {
super.subtreeChanged();
ControlFlowCache.clear(this);
if (myInstanceAttributes != null) {
myInstanceAttributes = null;
}
myPropertyCache = null;
}
@NotNull
@Override
public SearchScope getUseScope() {
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(this);
if (scopeOwner instanceof PyFunction) {
return new LocalSearchScope(scopeOwner);
}
return super.getUseScope();
}
@NotNull
@Override
public List<PyClassLikeType> getSuperClassTypes(@NotNull TypeEvalContext context) {
if (PyNames.FAKE_OLD_BASE.equals(getName())) {
return Collections.emptyList();
}
final PyClassStub stub = getStub();
final List<PyClassLikeType> result = new ArrayList<PyClassLikeType>();
if (stub != null) {
final PsiElement parent = stub.getParentStub().getPsi();
if (parent instanceof PyFile) {
final PyFile file = (PyFile)parent;
for (QualifiedName name : stub.getSuperClasses()) {
result.add(name != null ? classTypeFromQName(name, file, context) : null);
}
}
}
else {
for (PyExpression expression : getSuperClassExpressions()) {
expression = unfoldClass(expression);
if (expression instanceof PyKeywordArgument) {
continue;
}
final PyType type = context.getType(expression);
result.add(type instanceof PyClassLikeType ? (PyClassLikeType)type : null);
}
}
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(this);
if (result.isEmpty() && isValid() && !builtinCache.isBuiltin(this)) {
final String implicitSuperName = LanguageLevel.forElement(this).isPy3K() ? PyNames.OBJECT : PyNames.FAKE_OLD_BASE;
final PyClass implicitSuper = builtinCache.getClass(implicitSuperName);
if (implicitSuper != null) {
final PyType type = context.getType(implicitSuper);
if (type instanceof PyClassLikeType) {
result.add((PyClassLikeType)type);
}
}
}
return result;
}
@NotNull
@Override
public List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context) {
// TODO: Return different cached copies depending on the type eval context parameters
final CachedValuesManager manager = CachedValuesManager.getManager(getProject());
return manager.getParameterizedCachedValue(this, myCachedValueKey, myCachedAncestorsProvider, false, context);
}
@Nullable
@Override
public PyType getMetaClassType(@NotNull TypeEvalContext context) {
if (context.maySwitchToAST(this)) {
final PyExpression expression = getMetaClassExpression();
if (expression != null) {
final PyType type = context.getType(expression);
if (type != null) {
return type;
}
}
}
else {
final PyClassStub stub = getStub();
final QualifiedName name = stub != null ? stub.getMetaClass() : PyPsiUtils.asQualifiedName(getMetaClassExpression());
final PsiFile file = getContainingFile();
if (file instanceof PyFile) {
final PyFile pyFile = (PyFile)file;
if (name != null) {
return classTypeFromQName(name, pyFile, context);
}
}
}
final LanguageLevel level = LanguageLevel.forElement(this);
if (level.isOlderThan(LanguageLevel.PYTHON30)) {
final PsiFile file = getContainingFile();
if (file instanceof PyFile) {
final PyFile pyFile = (PyFile)file;
final PsiElement element = pyFile.getElementNamed(PyNames.DUNDER_METACLASS);
if (element instanceof PyTypedElement) {
return context.getType((PyTypedElement)element);
}
}
}
return null;
}
@Nullable
@Override
public PyExpression getMetaClassExpression() {
final LanguageLevel level = LanguageLevel.forElement(this);
if (level.isAtLeast(LanguageLevel.PYTHON30)) {
// Requires AST access
for (PyExpression expression : getSuperClassExpressions()) {
if (expression instanceof PyKeywordArgument) {
final PyKeywordArgument argument = (PyKeywordArgument)expression;
if (PyNames.METACLASS.equals(argument.getKeyword())) {
return argument.getValueExpression();
}
}
}
}
else {
final PyTargetExpression attribute = findClassAttribute(PyNames.DUNDER_METACLASS, false);
if (attribute != null) {
return attribute;
}
}
return null;
}
@NotNull
private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) {
final PyType thisType = context.getType(this);
if (thisType instanceof PyClassLikeType) {
try {
return mroLinearize((PyClassLikeType)thisType, new HashSet<PyClassLikeType>(), false, context);
}
catch (IllegalStateException ignored) {
}
}
return Collections.emptyList();
}
@NotNull
private List<PyClassLikeType> getOldStyleAncestorTypes(@NotNull TypeEvalContext context) {
final List<PyClassLikeType> results = new ArrayList<PyClassLikeType>();
final List<PyClassLikeType> toProcess = new ArrayList<PyClassLikeType>();
final Set<PyClassLikeType> seen = new HashSet<PyClassLikeType>();
final Set<PyClassLikeType> visited = new HashSet<PyClassLikeType>();
final PyType thisType = context.getType(this);
if (thisType instanceof PyClassLikeType) {
toProcess.add((PyClassLikeType)thisType);
}
while (!toProcess.isEmpty()) {
final PyClassLikeType currentType = toProcess.remove(0);
visited.add(currentType);
for (PyClassLikeType superType : currentType.getSuperClassTypes(context)) {
if (superType == null || !seen.contains(superType)) {
results.add(superType);
seen.add(superType);
}
if (superType != null && !visited.contains(superType)) {
toProcess.add(superType);
}
}
}
return results;
}
@Nullable
private static PsiElement getElementQNamed(@NotNull NameDefiner nameDefiner, @NotNull QualifiedName qualifiedName) {
final int componentCount = qualifiedName.getComponentCount();
final String fullName = qualifiedName.toString();
if (componentCount == 0) {
return null;
}
else if (componentCount == 1) {
PsiElement element = nameDefiner.getElementNamed(fullName);
if (element == null) {
element = PyBuiltinCache.getInstance(nameDefiner).getByName(fullName);
}
return element;
}
else {
final String name = qualifiedName.getLastComponent();
final QualifiedName containingQName = qualifiedName.removeLastComponent();
NameDefiner definer = nameDefiner;
for (String component : containingQName.getComponents()) {
PsiElement element = PyUtil.turnDirIntoInit(definer.getElementNamed(component));
if (element instanceof PyImportElement) {
element = ((PyImportElement)element).resolve();
}
if (element instanceof NameDefiner) {
definer = (NameDefiner)element;
}
else {
definer = null;
break;
}
}
if (definer != null) {
return definer.getElementNamed(name);
}
return null;
}
}
@Nullable
private static PyClassLikeType classTypeFromQName(@NotNull QualifiedName qualifiedName, @NotNull PyFile containingFile,
@NotNull TypeEvalContext context) {
final PsiElement element = getElementQNamed(containingFile, qualifiedName);
if (element instanceof PyTypedElement) {
final PyType type = context.getType((PyTypedElement)element);
if (type instanceof PyClassLikeType) {
return (PyClassLikeType)type;
}
}
return null;
}
}