Repository: python/mypy

Issue:
Base class function signature compatibility checks overly strict
Actual example of this code can be found in Python 3.6's enum with `Flag` and `IntFlag`.

**Minimal repro:**:
```py3
from typing import TypeVar

_T = TypeVar('_T')


class Base:
    def __or__(self: _T, other: _T) -> _T: ...
    def __and__(self: _T, other: _T) -> _T: ...
    def __xor__(self: _T, other: _T) -> _T: ...


class Other(int, Base):  # <-- errors here
    def __or__(self: _T, other: Union[int, _T]) -> _T: ...  # type: ignore
    def __and__(self: _T, other: Union[int, _T]) -> _T: ...  # type: ignore
    def __xor__(self: _T, other: Union[int, _T]) -> _T: ...  # type: ignore
```

**Expected behavior**: no errors.
**Actual behavior**:
```
f:11: error: Definition of "__and__" in base class "int" is incompatible with definition in base class "Base"
f:11: error: Definition of "__or__" in base class "int" is incompatible with definition in base class "Base"
f:11: error: Definition of "__xor__" in base class "int" is incompatible with definition in base class "Base"
```

The errors would be fine if `Other` didn't redefine the methods itself. In this case the incompatibility in base classes is irrelevant.


Discussion:
Keywords to help searching: self type, union type
Just ran into this at Dropbox.

Slightly more distilled repro:
```python
class A(object):
    def x(self):
        # type: () -> A
        return self

class B(object):
    def x(self):
        # type: () -> B
        return self

class C(A, B):
    def x(self):
        # type: () -> C
        return self

test.py:11: error: Definition of "x" in base class "A" is incompatible with definition in base class "B"
```

And equivalent compilable Java code:
```java
interface A {
    A x();
}
interface B {
    B x();
}
class C implements A, B {
    public C x() {
        return this;
    }
}
```

I presume mypy already has checks that C.x is compatible with A.x and B.x. Is it possible to resolve this issue by doing something along the lines of removing the check that A.x and B.x are compatible in the case where x is overridden? (Note, I don't really know what I'm talking about.)

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1602,7 +1602,10 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
         # Verify that inherited attributes are compatible.
         mro = typ.mro[1:]
         for i, base in enumerate(mro):
-            for name in base.names:
+            # Attributes defined in both the type and base are skipped.
+            # Normal checks for attribute compatibility should catch any problems elsewhere.
+            non_overridden_attrs = base.names.keys() - typ.names.keys()
+            for name in non_overridden_attrs:
                 for base2 in mro[i + 1:]:
                     # We only need to check compatibility of attributes from classes not
                     # in a subclass relationship. For subclasses, normal (single inheritance)
@@ -1867,6 +1870,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[
                         # Show only one error per variable
                         break
 
+            direct_bases = lvalue_node.info.direct_base_classes()
+            last_immediate_base = direct_bases[-1] if direct_bases else None
+
             for base in lvalue_node.info.mro[1:]:
                 # Only check __slots__ against the 'object'
                 # If a base class defines a Tuple of 3 elements, a child of
@@ -1890,7 +1896,10 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[
                         # Only show one error per variable; even if other
                         # base classes are also incompatible
                         return True
-                    break
+                    if base is last_immediate_base:
+                        # At this point, the attribute was found to be compatible with all
+                        # immediate parents.
+                        break
         return False
 
     def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type],


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -4009,7 +4009,7 @@ class B(A):
 main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
 main:5: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
 
-[case testClassIgnoreType]
+[case testClassIgnoreType_RedefinedAttributeAndGrandparentAttributeTypesNotIgnored]
 class A:
     x = 0
 class B(A):
@@ -4018,6 +4018,15 @@ class C(B):
     x = ''
 [out]
 
+[case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren]
+class A:
+    x = 0
+class B(A):
+    x = ''  # type: ignore
+class C(B):
+    x = ''  # type: ignore
+[out]
+
 [case testInvalidMetaclassStructure]
 class X(type): pass
 class Y(type): pass
diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test
--- a/test-data/unit/check-multiple-inheritance.test
+++ b/test-data/unit/check-multiple-inheritance.test
@@ -228,7 +228,7 @@ class B(A, int): pass
 from typing import Callable, TypeVar
 T = TypeVar('T')
 class A(B, C):
-    def f(self): pass
+    pass
 class B:
     @dec
     def f(self): pass
@@ -461,6 +461,121 @@ class Combo(Base2, Base1): ...
 [out]
 main:10: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
 
+[case testMultipleInheritance_NestedVariableOverriddenWithCompatibleType]
+from typing import TypeVar, Generic
+T = TypeVar('T', covariant=True)
+class GenericBase(Generic[T]):
+    pass
+class Base1:
+    Nested: GenericBase['Base1']
+class Base2:
+    Nested: GenericBase['Base2']
+class A(Base1, Base2):
+    Nested: GenericBase['A']
+[out]
+
+[case testMultipleInheritance_NestedVariableOverriddenWithIncompatibleType1]
+from typing import TypeVar, Generic
+T = TypeVar('T', covariant=True)
+class GenericBase(Generic[T]):
+    pass
+class Base1:
+    Nested: GenericBase['Base1']
+class Base2:
+    Nested: GenericBase['Base2']
+class A(Base1, Base2):
+    Nested: GenericBase['Base1']
+[out]
+main:10: error: Incompatible types in assignment (expression has type "GenericBase[Base1]", base class "Base2" defined the type as "GenericBase[Base2]")
+
+[case testMultipleInheritance_NestedVariableOverriddenWithIncompatibleType2]
+from typing import TypeVar, Generic
+T = TypeVar('T', covariant=True)
+class GenericBase(Generic[T]):
+    pass
+class Base1:
+    Nested: GenericBase['Base1']
+class Base2:
+    Nested: GenericBase['Base2']
+class A(Base1, Base2):
+    Nested: GenericBase['Base2']
+[out]
+main:10: error: Incompatible types in assignment (expression has type "GenericBase[Base2]", base class "Base1" defined the type as "GenericBase[Base1]")
+
+[case testMultipleInheritance_NestedVariableOverriddenWithCompatibleType]
+from typing import TypeVar, Generic
+T = TypeVar('T', covariant=True)
+class GenericBase(Generic[T]):
+    pass
+class Base1:
+    Nested: GenericBase['Base1']
+class Base2:
+    Nested: GenericBase['Base1']
+class A(Base1, Base2):
+    Nested: GenericBase['Base1']
+[out]
+
+[case testMultipleInheritance_MethodDefinitionsCompatibleWithOverride]
+from typing import TypeVar, Union
+_T = TypeVar('_T')
+
+class Flag:
+    def __or__(self: _T, other: _T) -> _T: ...
+
+# int defines __or__ as:
+#     def __or__(self, n: int) -> int: ...
+class IntFlag(int, Flag):
+    def __or__(self: _T, other: Union[int, _T]) -> _T: ...
+[out]
+
+[case testMultipleInheritance_MethodDefinitionsIncompatibleOverride]
+from typing import TypeVar, Union
+_T = TypeVar('_T')
+
+class Flag:
+    def __or__(self: _T, other: _T) -> _T: ...
+
+class IntFlag(int, Flag):
+    def __or__(self: _T, other: str) -> _T: ...
+[out]
+main:8: error: Argument 1 of "__or__" incompatible with supertype "Flag"
+
+[case testMultipleInheritance_MethodDefinitionsCompatibleNoOverride]
+from typing import TypeVar, Union
+_T = TypeVar('_T')
+
+class Flag:
+    def __or__(self: _T, other: _T) -> _T: ...
+
+class IntFlag(int, Flag):
+    pass
+[out]
+
+[case testMultipleInheritance_MethodsReturningSelfCompatible]
+class A(object):
+    def x(self) -> 'A':
+        return self
+
+class B(object):
+    def x(self) -> 'B':
+        return self
+
+class C(A, B):
+    def x(self) -> 'C':
+        return self
+
+[case testMultipleInheritance_MethodsReturningSelfIncompatible]
+class A(object):
+    def x(self) -> 'A':
+        return self
+
+class B(object):
+    def x(self) -> 'B':
+        return self
+
+class C(A, B): # E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
+    pass
+
 [case testNestedVariableRefersToSubclassOfAnotherNestedClass]
 class Mixin1:
     class Meta: