Merge remote-tracking branch 'origin/master' into mikhail.golubev/py-attribute-inference
[idea/community.git] / python / src / com / jetbrains / python / psi / impl / PyClassImpl.java
index 0fa1b0d740094809be91b834d0adce6352404b70..99d9e6cd7a157b7c329c4fd44b5a824e6add7a16 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -40,8 +40,10 @@ 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;
@@ -61,6 +63,12 @@ import static com.intellij.openapi.util.text.StringUtil.notNullize;
  * @author yole
  */
 public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyClass {
+  public static class MROException extends Exception {
+    public MROException(String s) {
+      super(s);
+    }
+  }
+
   public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
   private static final Object EVALUATING = new Object();
 
@@ -79,8 +87,29 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
     @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);
+      List<PyClassLikeType> 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);
     }
   }
 
@@ -95,7 +124,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
   private class NewStyleCachedValueProvider implements CachedValueProvider<Boolean> {
     @Override
     public Result<Boolean> compute() {
-      return new Result<Boolean>(calculateNewStyleClass(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
+      return new Result<Boolean>(calculateNewStyleClass(), PsiModificationTracker.MODIFICATION_COUNT);
     }
   }
 
@@ -202,7 +231,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
   @NotNull
   @Override
   public List<PyClass> getAncestorClasses() {
-    return getAncestorClasses(TypeEvalContext.codeInsightFallback());
+    return getAncestorClasses(TypeEvalContext.codeInsightFallback(getProject()));
   }
 
   @NotNull
@@ -232,7 +261,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
     if (superClassQName.equals(getQualifiedName())) {
       return true;
     }
-    for (PyClassLikeType type : getAncestorTypes(TypeEvalContext.codeInsightFallback())) {
+    for (PyClassLikeType type : getAncestorTypes(TypeEvalContext.codeInsightFallback(getProject()))) {
       if (type != null && superClassQName.equals(type.getClassQName())) {
         return true;
       }
@@ -280,7 +309,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
 
   @NotNull
   public PyClass[] getSuperClasses() {
-    final List<PyClassLikeType> superTypes = getSuperClassTypes(TypeEvalContext.codeInsightFallback());
+    final List<PyClassLikeType> superTypes = getSuperClassTypes(TypeEvalContext.codeInsightFallback(getProject()));
     if (superTypes.isEmpty()) {
       return EMPTY_ARRAY;
     }
@@ -320,7 +349,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
   }
 
   @NotNull
-  private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) {
+  private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) throws MROException {
     List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
     while (true) {
       // filter blank sequences
@@ -334,6 +363,11 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
       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);
+        if (head == null) {
+          seq.remove(0);
+          found = true;
+          break;
+        }
         boolean headInTails = false;
         for (List<PyClassLikeType> tailSeq : nonBlankSequences) {
           if (tailSeq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
@@ -351,31 +385,35 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
       }
       if (!found) {
         // Inconsistent hierarchy results in TypeError
-        throw new IllegalStateException("Inconsistent class hierarchy");
+        throw new MROException("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);
+      if (head != null) {
+        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
   }
 
 
-  private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, boolean addThisType, @NotNull TypeEvalContext context) {
+  private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type,
+                                                    boolean addThisType,
+                                                    @NotNull TypeEvalContext context) throws MROException {
     return mroLinearize(type, addThisType, context, new HashMap<PyClassLikeType, Object>());
   }
 
   @NotNull
   private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, boolean addThisType,
                                                     @NotNull TypeEvalContext context,
-                                                    @NotNull Map<PyClassLikeType, Object> cache) {
+                                                    @NotNull Map<PyClassLikeType, Object> cache) throws MROException {
     final Object computed = cache.get(type);
     if (computed == EVALUATING) {
-      throw new IllegalStateException("Circular class inheritance");
+      throw new MROException("Circular class inheritance");
     }
     if (computed != null) {
       //noinspection unchecked
@@ -1052,7 +1090,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
     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())) {
+    for (PyClassLikeType type : getOldStyleAncestorTypes(TypeEvalContext.codeInsightFallback(getProject()))) {
       if (type == null) {
         // unknown, assume new-style class
         return true;
@@ -1192,11 +1230,10 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
     final PyClassStub stub = getStub();
     final List<PyClassLikeType> result = new ArrayList<PyClassLikeType>();
     if (stub != null) {
-      final PsiElement parent = stub.getParentStub().getPsi();
-      if (parent instanceof PyFile) {
-        final PyFile file = (PyFile)parent;
+      final PsiFile file = getContainingFile();
+      if (file instanceof PyFile) {
         for (QualifiedName name : stub.getSuperClasses()) {
-          result.add(name != null ? classTypeFromQName(name, file, context) : null);
+          result.add(name != null ? classTypeFromQName(name, (PyFile)file, context) : null);
         }
       }
     }
@@ -1207,7 +1244,23 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
           continue;
         }
         final PyType type = context.getType(expression);
-        result.add(type instanceof PyClassLikeType ? (PyClassLikeType)type : null);
+        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);
@@ -1287,23 +1340,55 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
     else {
       final PyTargetExpression attribute = findClassAttribute(PyNames.DUNDER_METACLASS, false);
       if (attribute != null) {
-        return attribute;
+        return attribute.findAssignedValue();
       }
     }
     return null;
   }
 
   @NotNull
-  private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) {
+  private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException {
     final PyType thisType = context.getType(this);
     if (thisType instanceof PyClassLikeType) {
-      try {
-        return mroLinearize((PyClassLikeType)thisType, false, context);
+      final PyClassLikeType thisClassLikeType = (PyClassLikeType)thisType;
+      final List<PyClassLikeType> ancestorTypes = mroLinearize(thisClassLikeType, false, context, new HashMap<PyClassLikeType, Object>());
+      if (isOverriddenMRO(ancestorTypes, context)) {
+        ancestorTypes.add(null);
+      }
+      return ancestorTypes;
+    }
+    else {
+      return Collections.emptyList();
+    }
+  }
+
+  private boolean isOverriddenMRO(@NotNull List<PyClassLikeType> ancestorTypes, @NotNull TypeEvalContext context) {
+    final List<PyClass> classes = new ArrayList<PyClass>();
+    classes.add(this);
+    for (PyClassLikeType ancestorType : ancestorTypes) {
+      if (ancestorType instanceof PyClassType) {
+        final PyClassType classType = (PyClassType)ancestorType;
+        classes.add(classType.getPyClass());
       }
-      catch (IllegalStateException ignored) {
+    }
+
+    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();
+        final PyFunction mroMethod = metaClass.findMethodByName(PyNames.MRO, true);
+        if (mroMethod != null) {
+          final PyClass mroClass = mroMethod.getContainingClass();
+          if (mroClass != null && mroClass != typeClass) {
+            return true;
+          }
+        }
       }
     }
-    return Collections.emptyList();
+
+    return false;
   }
 
   @NotNull
@@ -1335,47 +1420,67 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
   }
 
   @Nullable
-  private static PsiElement getElementQNamed(@NotNull NameDefiner nameDefiner, @NotNull QualifiedName qualifiedName) {
+  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 = nameDefiner.getElementNamed(fullName);
+      PsiElement element = resolveTypeMember(type, fullName, context);
       if (element == null) {
-        element = PyBuiltinCache.getInstance(nameDefiner).getByName(fullName);
+        element = PyBuiltinCache.getInstance(file).getByName(fullName);
       }
       return element;
     }
     else {
       final String name = qualifiedName.getLastComponent();
       final QualifiedName containingQName = qualifiedName.removeLastComponent();
-      NameDefiner definer = nameDefiner;
+      PyType currentType = type;
       for (String component : containingQName.getComponents()) {
-        PsiElement element = PyUtil.turnDirIntoInit(definer.getElementNamed(component));
-        if (element instanceof PyImportElement) {
-          element = ((PyImportElement)element).resolve();
-        }
-        if (element instanceof NameDefiner) {
-          definer = (NameDefiner)element;
-        }
-        else {
-          definer = null;
-          break;
+        currentType = getMemberType(currentType, component, context);
+        if (currentType == null) {
+          return null;
         }
       }
-      if (definer != null) {
-        return definer.getElementNamed(name);
+      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);
+    final PsiElement element = getElementQNamed(containingFile, qualifiedName, context);
     if (element instanceof PyTypedElement) {
       final PyType type = context.getType((PyTypedElement)element);
       if (type instanceof PyClassLikeType) {