mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
1395 lines
48 KiB
Java
1395 lines
48 KiB
Java
/*
|
|
* 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.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.PyResolveContext;
|
|
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
|
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
|
|
import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
|
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(getProject()));
|
|
}
|
|
|
|
@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(getProject()))) {
|
|
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(getProject()));
|
|
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;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public PyFunction[] getMethods(final boolean inherited) {
|
|
final PyFunction[] thisClassFunctions =
|
|
getClassChildren(PythonDialectsTokenSetProvider.INSTANCE.getFunctionDeclarationTokens(), PyFunction.ARRAY_FACTORY);
|
|
if (!inherited) {
|
|
return thisClassFunctions;
|
|
}
|
|
// Map to get rid of duplicated (overwritten methods)
|
|
final Map<String, PyFunction> result = new HashMap<String, PyFunction>();
|
|
// We get classes in MRO order (hopefully), so last methods are last
|
|
for (final PyClass superClass : getSuperClasses()) {
|
|
for (final PyFunction function : superClass.getMethods(false)) {
|
|
final String functionName = function.getName();
|
|
if (functionName != null) {
|
|
result.put(functionName, function);
|
|
}
|
|
}
|
|
}
|
|
// We now need to add our own methods
|
|
for (final PyFunction function : thisClassFunctions) {
|
|
final String functionName = function.getName();
|
|
if (functionName != null) {
|
|
result.put(functionName, function);
|
|
}
|
|
}
|
|
final Collection<PyFunction> functionsToReturn = result.values();
|
|
return functionsToReturn.toArray(new PyFunction[functionsToReturn.size()]);
|
|
}
|
|
|
|
@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(false)) {
|
|
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(false);
|
|
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(false);
|
|
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(getProject()))) {
|
|
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 PsiFile file = getContainingFile();
|
|
if (file instanceof PyFile) {
|
|
for (QualifiedName name : stub.getSuperClasses()) {
|
|
result.add(name != null ? classTypeFromQName(name, (PyFile)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.findAssignedValue();
|
|
}
|
|
}
|
|
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 PyFile file, @NotNull QualifiedName qualifiedName, @NotNull TypeEvalContext context) {
|
|
final int componentCount = qualifiedName.getComponentCount();
|
|
final String fullName = qualifiedName.toString();
|
|
final PyType type = new PyModuleType(file);
|
|
if (componentCount == 0) {
|
|
return null;
|
|
}
|
|
else if (componentCount == 1) {
|
|
PsiElement element = resolveTypeMember(type, fullName, context);
|
|
if (element == null) {
|
|
element = PyBuiltinCache.getInstance(file).getByName(fullName);
|
|
}
|
|
return element;
|
|
}
|
|
else {
|
|
final String name = qualifiedName.getLastComponent();
|
|
final QualifiedName containingQName = qualifiedName.removeLastComponent();
|
|
PyType currentType = type;
|
|
for (String component : containingQName.getComponents()) {
|
|
currentType = getMemberType(currentType, component, context);
|
|
if (currentType == null) {
|
|
return null;
|
|
}
|
|
}
|
|
if (name != null) {
|
|
return resolveTypeMember(currentType, name, context);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private static PyType getMemberType(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) {
|
|
final PyType result;
|
|
PsiElement element = resolveTypeMember(type, name, context);
|
|
if (element instanceof PyImportedModule) {
|
|
result = new PyImportedModuleType((PyImportedModule)element);
|
|
}
|
|
else if (element instanceof PyTypedElement) {
|
|
result = context.getType((PyTypedElement)element);
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
if (result instanceof PyClassLikeType) {
|
|
return ((PyClassLikeType)result).toInstance();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiElement resolveTypeMember(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) {
|
|
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
|
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
|
|
return (results != null && !results.isEmpty()) ? results.get(0).getElement() : null;
|
|
}
|
|
|
|
@Nullable
|
|
private static PyClassLikeType classTypeFromQName(@NotNull QualifiedName qualifiedName, @NotNull PyFile containingFile,
|
|
@NotNull TypeEvalContext context) {
|
|
final PsiElement element = getElementQNamed(containingFile, qualifiedName, context);
|
|
if (element instanceof PyTypedElement) {
|
|
final PyType type = context.getType((PyTypedElement)element);
|
|
if (type instanceof PyClassLikeType) {
|
|
return (PyClassLikeType)type;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public PyClassLikeType getType(@NotNull TypeEvalContext context) {
|
|
return PyUtil.as(context.getType(this), PyClassLikeType.class);
|
|
}
|
|
}
|