Merge branch 'python-fixes'
authorAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Wed, 24 Dec 2014 14:02:46 +0000 (17:02 +0300)
committerAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Wed, 24 Dec 2014 14:02:46 +0000 (17:02 +0300)
python/psi-api/src/com/jetbrains/python/PyNames.java
python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
python/testData/codeInsight/classMRO/UnresolvedClassesImpossibleToBuildMRO.py [new file with mode: 0644]
python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py [new file with mode: 0644]
python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py [new file with mode: 0644]
python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py [new file with mode: 0644]
python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py [new file with mode: 0644]
python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PyResolveTest.java
python/testSrc/com/jetbrains/python/codeInsight/PyClassMROTest.java
python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java

index 21e5be632d59f7610ccfc6d8f212d75b8132fabb..20a37449454e32a3a87759c2a2ff61bf925a7ab7 100644 (file)
@@ -503,4 +503,6 @@ public class PyNames {
   public static final ImmutableSet<String> METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("__func__", "__self__");
 
   public static final ImmutableSet<String> LEGACY_METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("im_func", "im_self", "im_class");
+
+  public static final String MRO = "mro";
 }
index a94f11e3d2c93673624285194f9033ade465cec0..c03fe346b6e365d8b56884e9150520cb450aa1a2 100644 (file)
@@ -63,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 List<PyTargetExpression> myInstanceAttributes;
@@ -80,7 +86,28 @@ 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);
+      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);
     }
   }
@@ -321,7 +348,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
@@ -335,6 +362,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 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.
@@ -352,14 +384,16 @@ 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
@@ -367,9 +401,9 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
 
   @NotNull
   private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, @NotNull Set<PyClassLikeType> seen, boolean addThisType,
-                                                    @NotNull TypeEvalContext context) {
+                                                    @NotNull TypeEvalContext context) throws MROException {
     if (seen.contains(type)) {
-      throw new IllegalStateException("Circular class inheritance");
+      throw new MROException("Circular class inheritance");
     }
     final List<PyClassLikeType> bases = type.getSuperClassTypes(context);
     List<List<PyClassLikeType>> lines = new ArrayList<List<PyClassLikeType>>();
@@ -1293,16 +1327,48 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
   }
 
   @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, new HashSet<PyClassLikeType>(), false, context);
+      final PyClassLikeType thisClassLikeType = (PyClassLikeType)thisType;
+      final List<PyClassLikeType> ancestorTypes = mroLinearize(thisClassLikeType, new HashSet<PyClassLikeType>(), false, context);
+      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
diff --git a/python/testData/codeInsight/classMRO/UnresolvedClassesImpossibleToBuildMRO.py b/python/testData/codeInsight/classMRO/UnresolvedClassesImpossibleToBuildMRO.py
new file mode 100644 (file)
index 0000000..9514c0f
--- /dev/null
@@ -0,0 +1,34 @@
+class EtagSupport(object):
+    pass
+
+
+class LockableItem(EtagSupport):
+    pass
+
+
+class Resource(LockableItem, _Unresolved):
+    pass
+
+
+class CopyContainer(_Unresolved):
+    pass
+
+
+class Navigation(_Unresolved):
+    pass
+
+
+class Tabs(_Unresolved):
+    pass
+
+
+class Collection(Resource):
+    pass
+
+
+class Traversable(object):
+    pass
+
+
+class ObjectManager(CopyContainer, Navigation, Tabs, _Unresolved, _Unresolved, Collection, Traversable):
+    pass
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py b/python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py
new file mode 100644 (file)
index 0000000..edd8344
--- /dev/null
@@ -0,0 +1,22 @@
+class X(<error descr="Unresolved reference 'Unresolved'">Unresolved</error>):
+    pass
+
+
+class Y(<error descr="Unresolved reference 'Unresolved'">Unresolved</error>):
+    pass
+
+
+class A(X, Y):
+    def foo(self):
+        pass
+
+
+class B(Y, X):
+    pass
+
+
+class C(A, B):  # we don't know whether MRO is OK or not
+    pass
+
+
+print(C.foo)  # pass
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py b/python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py
new file mode 100644 (file)
index 0000000..600ec8e
--- /dev/null
@@ -0,0 +1,26 @@
+class O(object):
+    pass
+
+
+class X(O):
+    pass
+
+
+class Y(O):
+    pass
+
+
+class A(X, Y):
+    def foo(self):
+        pass
+
+
+class B(Y, X):
+    pass
+
+
+class C(A, B):  # bad MRO
+    pass
+
+
+print(C.foo)  # pass
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py
new file mode 100644 (file)
index 0000000..e1f25b3
--- /dev/null
@@ -0,0 +1,22 @@
+class A(object):
+    def foo(self):
+        return 0
+
+
+class B(object):
+    def bar(self):
+        return 0
+
+
+class MyMeta(type):
+    def mro(cls):
+        return A, B
+
+
+class C(B):
+    __metaclass__ = MyMeta
+
+
+c = C()
+print(c.foo().lower())  # pass
+print(c.bar().<warning descr="Unresolved attribute reference 'lower' for class 'int'">lower</warning>())
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py
new file mode 100644 (file)
index 0000000..15cd8ae
--- /dev/null
@@ -0,0 +1,28 @@
+class A(object):
+    def foo(self):
+        return 0
+
+
+class MyMeta(type):
+    def mro(cls):
+        return A, B
+
+
+class MyMeta2(MyMeta):
+    pass
+
+
+class B(object):
+    __metaclass__ = MyMeta2
+
+    def bar(self):
+        return 0
+
+
+class C(B):
+    pass
+
+
+c = C()
+print(c.foo().lower())  # pass
+print(c.bar().<warning descr="Unresolved attribute reference 'lower' for class 'int'">lower</warning>())
diff --git a/python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py b/python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py
new file mode 100644 (file)
index 0000000..c279d67
--- /dev/null
@@ -0,0 +1,23 @@
+class X(Unresolved):
+    pass
+
+
+class Y(Unresolved):
+    pass
+
+
+class A(X, Y):
+    def foo(self):
+        pass
+
+
+class B(Y, X):
+    pass
+
+
+class C(A, B):  # we don't know whether MRO is OK or not
+    pass
+
+
+print(C.foo)
+#       <ref>
index 91a8955938cfc7965a379d695ce0eef4b956967b..52089593aec6dc73464de557a76147c8a459ba32 100644 (file)
@@ -567,4 +567,9 @@ public class PyResolveTest extends PyResolveTestCase {
     PyTargetExpression xyzzy = assertResolvesTo(PyTargetExpression.class, "xyzzy");
     assertEquals("__init__", PsiTreeUtil.getParentOfType(xyzzy, PyFunction.class).getName());
   }
+
+  // PY-11401
+  public void testResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails() {
+    assertResolvesTo(PyFunction.class, "foo");
+  }
 }
index eb828b7eed8c449685775f3451efd99ad878f3dc..a6a2adc488c5ad5352a7f99d6fa75c623427ea14 100644 (file)
@@ -35,7 +35,7 @@ public class PyClassMROTest extends PyTestCase {
 
   // TypeError in Python
   public void testMROConflict() {
-    assertMRO(getClass("C"));
+    assertMRO(getClass("C"), "unknown");
   }
 
   public void testCircularInheritance() {
@@ -43,7 +43,7 @@ public class PyClassMROTest extends PyTestCase {
     myFixture.configureByFiles(getPath(testName), getPath(testName + "2"));
     final PyClass cls = myFixture.findElementByText("Foo", PyClass.class);
     assertNotNull(cls);
-    assertMRO(cls);
+    assertMRO(cls, "unknown");
   }
 
   public void testExampleFromDoc1() {
@@ -55,7 +55,7 @@ public class PyClassMROTest extends PyTestCase {
   }
 
   public void testExampleFromDoc3() {
-    assertMRO(getClass("G"));
+    assertMRO(getClass("G"), "unknown");
   }
 
   public void testExampleFromDoc4() {
@@ -71,6 +71,13 @@ public class PyClassMROTest extends PyTestCase {
     assertMRO(getClass("H"), "E", "F", "B", "G", "C", "D", "A", "object");
   }
 
+  // PY-11401
+  public void testUnresolvedClassesImpossibleToBuildMRO() {
+    assertMRO(getClass("ObjectManager"),
+              "CopyContainer", "unknown", "Navigation", "unknown", "Tabs", "unknown", "unknown", "unknown", "Collection", "Resource",
+              "LockableItem", "EtagSupport", "Traversable", "object", "unknown");
+  }
+
   public void assertMRO(@NotNull PyClass cls, @NotNull String... mro) {
     final List<PyClassLikeType> types = cls.getAncestorTypes(TypeEvalContext.codeInsightFallback(cls.getProject()));
     final List<String> classNames = new ArrayList<String>();
index b367fcb8089f02e95f9dafc057d85c2c44fdd52a..327cecabf147f550ebc4752b4d2dc3f18ff041ab 100644 (file)
@@ -440,6 +440,26 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
     doMultiFileTest("p1/__init__.py");
   }
 
+  // PY-11401
+  public void testNoUnresolvedReferencesForClassesWithBadMRO() {
+    doTest();
+  }
+
+  // PY-11401
+  public void testFallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails() {
+    doTest();
+  }
+
+  // PY-11401
+  public void testOverriddenMRO() {
+    doTest();
+  }
+
+  // PY-11401
+  public void testOverriddenMROInAncestors() {
+    doTest();
+  }
+
   @NotNull
   @Override
   protected Class<? extends PyInspection> getInspectionClass() {