From 8e20acc0ef7c3489c353b53bd4f135937d32a20f Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Sun, 24 Mar 2019 18:48:59 +0100 Subject: [PATCH 1/4] Loosen base class attribute compatibility checks when attribute is redefined - In the case of multiple inheritance, don't give errors about definitions of an attribute in base classes being incompatible when the attribute is redefined. The redefinition must itself be compatible with all (non-type-ignored) definitions of the attribute in all base classes. This is achieved by making the following change to checking of incompatible types in assignments. - Don't stop checking after the first base where the attribute is defined when checking for incompatible types in assignments. There is still a maximum of one "Incompatible type in assignment" error per assignment. Resolves #2619 --- mypy/checker.py | 6 +- test-data/unit/check-classes.test | 14 +- .../unit/check-multiple-inheritance.test | 124 +++++++++++++++++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b721c8690fc7..94379901d051 100644 --- 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) @@ -1890,7 +1893,6 @@ 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 return False def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type], diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ccc683088cde..0620004c9a56 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3725,6 +3725,7 @@ class C(B): a = "a" [out] main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") +main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableSubclassTypeOverwriteImplicit] class A: @@ -4009,7 +4010,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): @@ -4017,6 +4018,17 @@ class B(A): class C(B): x = '' [out] +main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") + + +[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 diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index 1bdc70c19fa1..a0b367918dc1 100644 --- 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,128 @@ 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:11: 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:11: 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: From 8adafdb664946e296c33739fca0bb56b9657e085 Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Wed, 10 Apr 2019 22:13:44 +0200 Subject: [PATCH 2/4] Remove extra newlines. --- test-data/unit/check-classes.test | 1 - test-data/unit/check-multiple-inheritance.test | 11 ++--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0620004c9a56..e7e2d7f6f519 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4020,7 +4020,6 @@ class C(B): [out] main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") - [case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren] class A: x = 0 diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index a0b367918dc1..d87b4a5c4819 100644 --- a/test-data/unit/check-multiple-inheritance.test +++ b/test-data/unit/check-multiple-inheritance.test @@ -462,7 +462,6 @@ class Combo(Base2, Base1): ... 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]): @@ -476,7 +475,6 @@ class A(Base1, Base2): [out] [case testMultipleInheritance_NestedVariableOverriddenWithIncompatibleType1] - from typing import TypeVar, Generic T = TypeVar('T', covariant=True) class GenericBase(Generic[T]): @@ -488,10 +486,9 @@ class Base2: class A(Base1, Base2): Nested: GenericBase['Base1'] [out] -main:11: error: Incompatible types in assignment (expression has type "GenericBase[Base1]", base class "Base2" defined the type as "GenericBase[Base2]") +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]): @@ -503,11 +500,9 @@ class Base2: class A(Base1, Base2): Nested: GenericBase['Base2'] [out] -main:11: error: Incompatible types in assignment (expression has type "GenericBase[Base2]", base class "Base1" defined the type as "GenericBase[Base1]") - +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]): @@ -520,8 +515,6 @@ class A(Base1, Base2): Nested: GenericBase['Base1'] [out] - - [case testMultipleInheritance_MethodDefinitionsCompatibleWithOverride] from typing import TypeVar, Union _T = TypeVar('_T') From 3a38d4b320f0fe58db129eeadf1c98476b0746ea Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Thu, 11 Apr 2019 22:24:23 +0200 Subject: [PATCH 3/4] Restore the current behavior regarding incompatible type in assignment. --- mypy/checker.py | 6 ++++++ test-data/unit/check-classes.test | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 94379901d051..ba358e085997 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1870,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 @@ -1893,6 +1896,9 @@ 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 + if base == 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], diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e7e2d7f6f519..0dab5b687773 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3725,7 +3725,6 @@ class C(B): a = "a" [out] main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") -main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableSubclassTypeOverwriteImplicit] class A: @@ -4018,7 +4017,6 @@ class B(A): class C(B): x = '' [out] -main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren] class A: From e807d81c4bc4064a7ae520a9e853dfcb6ec41235 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 12 Apr 2019 14:52:55 +0100 Subject: [PATCH 4/4] Style fixes --- mypy/checker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ba358e085997..c4e3f8c5b239 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1896,8 +1896,9 @@ 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 - if base == last_immediate_base: - # At this point, the attribute was found to be compatible with all immediate parents, + if base is last_immediate_base: + # At this point, the attribute was found to be compatible with all + # immediate parents. break return False