All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.psi.impl.PyClassImpl Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.openapi.util.Ref;
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 implements PyClass {
  public static class MROException extends Exception {
    public MROException(String s) {
      super(s);
    }
  }

  public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];

  private List myInstanceAttributes;
  private final NotNullLazyValue> myNewStyle = new NotNullLazyValue>() {
    @NotNull
    @Override
    protected CachedValue compute() {
      return CachedValuesManager.getManager(getProject()).createCachedValue(new NewStyleCachedValueProvider(), false);
    }
  };

  private volatile Map myPropertyCache;

  private class CachedAncestorsProvider implements ParameterizedCachedValueProvider, TypeEvalContext> {
    @Nullable
    @Override
    public CachedValueProvider.Result> compute(@NotNull TypeEvalContext context) {
      List ancestorTypes;
      if (isNewStyleClass()) {
        try {
          ancestorTypes = getMROAncestorTypes(context);
        }
        catch (MROException e) {
          ancestorTypes = getOldStyleAncestorTypes(context);
          boolean hasUnresolvedAncestorTypes = false;
          for (PyClassLikeType type : ancestorTypes) {
            if (type == null) {
              hasUnresolvedAncestorTypes = true;
              break;
            }
          }
          if (!hasUnresolvedAncestorTypes) {
            ancestorTypes = Collections.singletonList(null);
          }
        }
      }
      else {
        ancestorTypes = getOldStyleAncestorTypes(context);
      }
      return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.MODIFICATION_COUNT);
    }
  }

  private final Key, 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 {
    @Override
    public Result compute() {
      return new Result(calculateNewStyleClass(), PsiModificationTracker.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 getAncestorClasses() {
    return getAncestorClasses(TypeEvalContext.codeInsightFallback(getProject()));
  }

  @NotNull
  @Override
  public List getAncestorClasses(@NotNull TypeEvalContext context) {
    final List results = new ArrayList();
    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 getSlots() {
    final Set result = new LinkedHashSet();
    boolean found = false;
    final List ownSlots = getOwnSlots();
    if (ownSlots != null) {
      found = true;
      result.addAll(ownSlots);
    }
    for (PyClass cls : getAncestorClasses()) {
      final List ancestorSlots = cls.getOwnSlots();
      if (ancestorSlots != null) {
        found = true;
        result.addAll(ancestorSlots);
      }
    }
    return found ? new ArrayList(result) : null;
  }

  @Nullable
  @Override
  public List getOwnSlots() {
    final PyClassStub stub = getStub();
    if (stub != null) {
      return stub.getSlots();
    }
    return PyFileImpl.getStringListFromTargetExpression(PyNames.SLOTS, getClassAttributes());
  }

  @NotNull
  public PyClass[] getSuperClasses() {
    final List superTypes = getSuperClassTypes(TypeEvalContext.codeInsightFallback(getProject()));
    if (superTypes.isEmpty()) {
      return EMPTY_ARRAY;
    }
    final List result = new ArrayList();
    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() {
            public String fun(PyExpression expr) {
              String name = expr.getText();
              return notNullize(name, PyNames.UNNAMED_ELEMENT);
            }
          }, ", "));
          result.append(")");
        }
        return result.toString();
      }
    };
  }

  @NotNull
  private static List mroMerge(@NotNull List> sequences) throws MROException {
    List result = new LinkedList(); // need to insert to 0th position on linearize
    while (true) {
      // filter blank sequences
      final List> nonBlankSequences = new ArrayList>(sequences.size());
      for (List 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 seq : nonBlankSequences) {
        head = seq.get(0);
        if (head == null) {
          seq.remove(0);
          found = true;
          break;
        }
        boolean headInTails = false;
        for (List tailSeq : nonBlankSequences) {
          if (tailSeq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
            headInTails = true;
            break;
          }
        }
        if (!headInTails) {
          found = true;
          break;
        }
        else {
          head = null; // as a signal
        }
      }
      if (!found) {
        // Inconsistent hierarchy results in TypeError
        throw new MROException("Inconsistent class hierarchy");
      }
      // our head is clean;
      result.add(head);
      // remove it from heads of other sequences
      if (head != null) {
        for (List 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 mroLinearize(@NotNull PyClassLikeType type,
                                                    boolean addThisType,
                                                    @NotNull TypeEvalContext context,
                                                    @NotNull Map>> cache) throws MROException {
    final Ref> computed = cache.get(type);
    if (computed != null) {
      if (computed.isNull()) {
        throw new MROException("Circular class inheritance");
      }
      return computed.get();
    }
    cache.put(type, Ref.>create());
    List result = null;
    try {
      final List bases = type.getSuperClassTypes(context);
      final List> lines = new ArrayList>();
      for (PyClassLikeType base : bases) {
        if (base != null) {
          final List baseClassMRO = mroLinearize(base, true, context, cache);
          if (!baseClassMRO.isEmpty()) {
            lines.add(baseClassMRO);
          }
        }
      }
      if (!bases.isEmpty()) {
        lines.add(bases);
      }
      result = mroMerge(lines);
      if (addThisType) {
        result.add(0, type);
      }
    }
    finally {
      cache.put(type, Ref.create(result));
    }
    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 result = new HashMap();
    // 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 functionsToReturn = result.values();
    return functionsToReturn.toArray(new PyFunction[functionsToReturn.size()]);
  }

  @Override
  @NotNull
  public Map getProperties() {
    initProperties();
    return new HashMap(myPropertyCache);
  }

  @Override
  public PyClass[] getNestedClasses() {
    return getClassChildren(TokenSet.create(PyElementTypes.CLASS_DECLARATION), PyClass.ARRAY_FACTORY);
  }

  protected  T[] getClassChildren(TokenSet elementTypes, ArrayFactory factory) {
    // TODO: gather all top-level functions, maybe within control statements
    final PyClassStub classStub = getStub();
    if (classStub != null) {
      return classStub.getChildrenByType(elementTypes, factory);
    }
    List result = new ArrayList();
    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 implements Processor {
    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 proc = new NameFinder(name);
    visitMethods(proc, inherited);
    return proc.getResult();
  }

  @Nullable
  @Override
  public PyClass findNestedClass(String name, boolean inherited) {
    if (name == null) return null;
    NameFinder proc = new NameFinder(name);
    visitNestedClasses(proc, inherited);
    return proc.getResult();
  }

  @Nullable
  public PyFunction findInitOrNew(boolean inherited) {
    NameFinder proc;
    if (isNewStyleClass()) {
      proc = new NameFinder(PyNames.INIT, PyNames.NEW);
    }
    else {
      proc = new NameFinder(PyNames.INIT);
    }
    visitMethods(proc, inherited, true);
    return proc.getResult();
  }

  private final static Maybe UNKNOWN_CALL = new Maybe(); // denotes _not_ a PyFunction, actually
  private final static Maybe NONE = new Maybe(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_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 filter, boolean useAdvancedSyntax) {
    // look at @property decorators
    Map> grouped = new HashMap>();
    // 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 bucket = grouped.get(methodName);
        if (bucket == null) {
          bucket = new SmartList();
          grouped.put(methodName, bucket);
        }
        bucket.add(method);
      }
    }
    for (Map.Entry> entry : grouped.entrySet()) {
      Maybe getter = NONE;
      Maybe setter = NONE;
      Maybe 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(method);
              }
              else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.GETTER)) {
                getter = new Maybe(method);
              }
              else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.SETTER)) {
                setter = new Maybe(method);
              }
              else if (useAdvancedSyntax && qname.matches(decoratorName, PyNames.DELETER)) {
                deleter = new Maybe(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 fromPacked(Maybe 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(method);
    }
    return UNKNOWN_CALL;
  }

  @Nullable
  private Property processStubProperties(@Nullable String name, @Nullable Processor 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 getter = fromPacked(prop.getGetter());
            Maybe setter = fromPacked(prop.getSetter());
            Maybe 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(PyCallable 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 initializePropertyCache() {
    final Map result = new HashMap();
    processProperties(null, new Processor() {
      @Override
      public boolean process(Property property) {
        result.put(property.getName(), property);
        return false;
      }
    }, false);
    return result;
  }

  @Nullable
  @Override
  public Property scanProperties(@Nullable Processor filter, boolean inherited) {
    return processProperties(null, filter, inherited);
  }

  @Nullable
  private Property processProperties(@Nullable String name, @Nullable Processor 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 implements Property {
    private final String myName;

    private PropertyImpl(String name,
                         Maybe getter,
                         Maybe setter,
                         Maybe deleter,
                         String doc,
                         PyTargetExpression site) {
      myName = name;
      myDeleter = deleter;
      myGetter = getter;
      mySetter = setter;
      myDoc = doc;
      mySite = site;
    }

    @NotNull
    @Override
    public Maybe getGetter() {
      return filterNonStubExpression(myGetter);
    }

    @NotNull
    @Override
    public Maybe getSetter() {
      return filterNonStubExpression(mySetter);
    }

    @NotNull
    @Override
    public Maybe getDeleter() {
      return filterNonStubExpression(myDeleter);
    }

    public String getName() {
      return myName;
    }

    public PyTargetExpression getDefinitionSite() {
      return mySite;
    }

    @NotNull
    @Override
    public Maybe 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 PyCallable 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 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 PyCallable) {
        return new Maybe((PyCallable)expr);
      }
      final PsiReference ref = expr.getReference();
      if (ref != null) {
        PsiElement something = ref.resolve();
        if (something instanceof PyCallable) {
          return new Maybe((PyCallable)something);
        }
      }
      return NONE;
    }

    @NotNull
    private static Maybe filterNonStubExpression(@NotNull Maybe maybeCallable) {
      final PyCallable 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 processor, boolean inherited) {
    return visitMethods(processor, inherited, false);
  }

  public boolean visitMethods(Processor 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 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 processor, boolean inherited) {
    List 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 getClassAttributes() {
    PyClassStub stub = getStub();
    if (stub != null) {
      final PyTargetExpression[] children = stub.getChildrenByType(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.EMPTY_ARRAY);
      return Arrays.asList(children);
    }
    List result = new ArrayList();
    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 processor = new NameFinder(name);
    visitClassAttributes(processor, inherited);
    return processor.getResult();
  }

  public List getInstanceAttributes() {
    if (myInstanceAttributes == null) {
      myInstanceAttributes = collectInstanceAttributes();
    }
    return myInstanceAttributes;
  }

  @Nullable
  @Override
  public PyTargetExpression findInstanceAttribute(String name, boolean inherited) {
    final List 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 collectInstanceAttributes() {
    Map result = new HashMap();

    collectAttributesInNew(result);
    PyFunctionImpl initMethod = (PyFunctionImpl)findMethodByName(PyNames.INIT, false);
    if (initMethod != null) {
      collectInstanceAttributes(initMethod, result);
    }
    Set namesInInit = new HashSet(result.keySet());
    final PyFunction[] methods = getMethods(false);
    for (PyFunction method : methods) {
      if (!PyNames.INIT.equals(method.getName())) {
        collectInstanceAttributes(method, result, namesInInit);
      }
    }

    final Collection expressions = result.values();
    return new ArrayList(expressions);
  }

  private void collectAttributesInNew(@NotNull final Map 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 result) {
    collectInstanceAttributes(method, result, null);
  }

  public static void collectInstanceAttributes(@NotNull PyFunction method,
                                               @NotNull final Map result,
                                               Set 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 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 result = new ArrayList();
      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 || 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 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 declarationsInMethod = new HashMap();
    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 iterateNames() {
    return Collections.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 getSuperClassTypes(@NotNull TypeEvalContext context) {
    if (PyNames.FAKE_OLD_BASE.equals(getName())) {
      return Collections.emptyList();
    }
    final PyClassStub stub = getStub();
    final List result = new ArrayList();
    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()) {
        context.getType(expression);
        expression = unfoldClass(expression);
        if (expression instanceof PyKeywordArgument) {
          continue;
        }
        final PyType type = context.getType(expression);
        PyClassLikeType classLikeType = null;
        if (type instanceof PyClassLikeType) {
          classLikeType = (PyClassLikeType)type;
        }
        else {
          final PsiReference ref = expression.getReference();
          if (ref != null) {
            final PsiElement resolved = ref.resolve();
            if (resolved instanceof PyClass) {
              final PyType resolvedType = context.getType((PyClass)resolved);
              if (resolvedType instanceof PyClassLikeType) {
                classLikeType = (PyClassLikeType)resolvedType;
              }
            }
          }
        }
        result.add(classLikeType);
      }
    }
    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 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 getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException {
    final PyType thisType = context.getType(this);
    if (thisType instanceof PyClassLikeType) {
      final PyClassLikeType thisClassLikeType = (PyClassLikeType)thisType;
      final List ancestorTypes =
        mroLinearize(thisClassLikeType, false, context, new HashMap>>());
      if (isOverriddenMRO(ancestorTypes, context)) {
        ancestorTypes.add(null);
      }
      return ancestorTypes;
    }
    else {
      return Collections.emptyList();
    }
  }

  private boolean isOverriddenMRO(@NotNull List ancestorTypes, @NotNull TypeEvalContext context) {
    final List classes = new ArrayList();
    classes.add(this);
    for (PyClassLikeType ancestorType : ancestorTypes) {
      if (ancestorType instanceof PyClassType) {
        final PyClassType classType = (PyClassType)ancestorType;
        classes.add(classType.getPyClass());
      }
    }

    final PyClass typeClass = PyBuiltinCache.getInstance(this).getClass("type");

    for (PyClass cls : classes) {
      final PyType metaClassType = cls.getMetaClassType(context);
      if (metaClassType instanceof PyClassType) {
        final PyClass metaClass = ((PyClassType)metaClassType).getPyClass();
        if (cls == metaClass) {
          return false;
        }
        final PyFunction mroMethod = metaClass.findMethodByName(PyNames.MRO, true);
        if (mroMethod != null) {
          final PyClass mroClass = mroMethod.getContainingClass();
          if (mroClass != null && mroClass != typeClass) {
            return true;
          }
        }
      }
    }

    return false;
  }

  @NotNull
  private List getOldStyleAncestorTypes(@NotNull TypeEvalContext context) {
    final List results = new ArrayList();
    final Deque toProcess = new LinkedList();
    final Set seen = new HashSet();
    final Set visited = new HashSet();
    final PyType thisType = context.getType(this);
    if (thisType instanceof PyClassLikeType) {
      toProcess.add((PyClassLikeType)thisType);
    }
    while (!toProcess.isEmpty()) {
      final PyClassLikeType currentType = toProcess.pollFirst();
      if (!visited.add(currentType)) {
        continue;
      }
      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.addLast(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 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);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy