Skip to content

Commit 8e20acc

Browse files
committed
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
1 parent a642784 commit 8e20acc

File tree

3 files changed

+140
-4
lines changed

3 files changed

+140
-4
lines changed

mypy/checker.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,7 +1602,10 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
16021602
# Verify that inherited attributes are compatible.
16031603
mro = typ.mro[1:]
16041604
for i, base in enumerate(mro):
1605-
for name in base.names:
1605+
# Attributes defined in both the type and base are skipped.
1606+
# Normal checks for attribute compatibility should catch any problems elsewhere.
1607+
non_overridden_attrs = base.names.keys() - typ.names.keys()
1608+
for name in non_overridden_attrs:
16061609
for base2 in mro[i + 1:]:
16071610
# We only need to check compatibility of attributes from classes not
16081611
# in a subclass relationship. For subclasses, normal (single inheritance)
@@ -1890,7 +1893,6 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[
18901893
# Only show one error per variable; even if other
18911894
# base classes are also incompatible
18921895
return True
1893-
break
18941896
return False
18951897

18961898
def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type],

test-data/unit/check-classes.test

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3725,6 +3725,7 @@ class C(B):
37253725
a = "a"
37263726
[out]
37273727
main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
3728+
main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
37283729

37293730
[case testVariableSubclassTypeOverwriteImplicit]
37303731
class A:
@@ -4009,14 +4010,25 @@ class B(A):
40094010
main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
40104011
main:5: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
40114012

4012-
[case testClassIgnoreType]
4013+
[case testClassIgnoreType_RedefinedAttributeAndGrandparentAttributeTypesNotIgnored]
40134014
class A:
40144015
x = 0
40154016
class B(A):
40164017
x = '' # type: ignore
40174018
class C(B):
40184019
x = ''
40194020
[out]
4021+
main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
4022+
4023+
4024+
[case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren]
4025+
class A:
4026+
x = 0
4027+
class B(A):
4028+
x = '' # type: ignore
4029+
class C(B):
4030+
x = '' # type: ignore
4031+
[out]
40204032

40214033
[case testInvalidMetaclassStructure]
40224034
class X(type): pass

test-data/unit/check-multiple-inheritance.test

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class B(A, int): pass
228228
from typing import Callable, TypeVar
229229
T = TypeVar('T')
230230
class A(B, C):
231-
def f(self): pass
231+
pass
232232
class B:
233233
@dec
234234
def f(self): pass
@@ -461,6 +461,128 @@ class Combo(Base2, Base1): ...
461461
[out]
462462
main:10: error: Definition of "NestedVar" in base class "Base2" is incompatible with definition in base class "Base1"
463463

464+
[case testMultipleInheritance_NestedVariableOverriddenWithCompatibleType]
465+
466+
from typing import TypeVar, Generic
467+
T = TypeVar('T', covariant=True)
468+
class GenericBase(Generic[T]):
469+
pass
470+
class Base1:
471+
Nested: GenericBase['Base1']
472+
class Base2:
473+
Nested: GenericBase['Base2']
474+
class A(Base1, Base2):
475+
Nested: GenericBase['A']
476+
[out]
477+
478+
[case testMultipleInheritance_NestedVariableOverriddenWithIncompatibleType1]
479+
480+
from typing import TypeVar, Generic
481+
T = TypeVar('T', covariant=True)
482+
class GenericBase(Generic[T]):
483+
pass
484+
class Base1:
485+
Nested: GenericBase['Base1']
486+
class Base2:
487+
Nested: GenericBase['Base2']
488+
class A(Base1, Base2):
489+
Nested: GenericBase['Base1']
490+
[out]
491+
main:11: error: Incompatible types in assignment (expression has type "GenericBase[Base1]", base class "Base2" defined the type as "GenericBase[Base2]")
492+
493+
[case testMultipleInheritance_NestedVariableOverriddenWithIncompatibleType2]
494+
495+
from typing import TypeVar, Generic
496+
T = TypeVar('T', covariant=True)
497+
class GenericBase(Generic[T]):
498+
pass
499+
class Base1:
500+
Nested: GenericBase['Base1']
501+
class Base2:
502+
Nested: GenericBase['Base2']
503+
class A(Base1, Base2):
504+
Nested: GenericBase['Base2']
505+
[out]
506+
main:11: error: Incompatible types in assignment (expression has type "GenericBase[Base2]", base class "Base1" defined the type as "GenericBase[Base1]")
507+
508+
509+
[case testMultipleInheritance_NestedVariableOverriddenWithCompatibleType]
510+
511+
from typing import TypeVar, Generic
512+
T = TypeVar('T', covariant=True)
513+
class GenericBase(Generic[T]):
514+
pass
515+
class Base1:
516+
Nested: GenericBase['Base1']
517+
class Base2:
518+
Nested: GenericBase['Base1']
519+
class A(Base1, Base2):
520+
Nested: GenericBase['Base1']
521+
[out]
522+
523+
524+
525+
[case testMultipleInheritance_MethodDefinitionsCompatibleWithOverride]
526+
from typing import TypeVar, Union
527+
_T = TypeVar('_T')
528+
529+
class Flag:
530+
def __or__(self: _T, other: _T) -> _T: ...
531+
532+
# int defines __or__ as:
533+
# def __or__(self, n: int) -> int: ...
534+
class IntFlag(int, Flag):
535+
def __or__(self: _T, other: Union[int, _T]) -> _T: ...
536+
[out]
537+
538+
[case testMultipleInheritance_MethodDefinitionsIncompatibleOverride]
539+
from typing import TypeVar, Union
540+
_T = TypeVar('_T')
541+
542+
class Flag:
543+
def __or__(self: _T, other: _T) -> _T: ...
544+
545+
class IntFlag(int, Flag):
546+
def __or__(self: _T, other: str) -> _T: ...
547+
[out]
548+
main:8: error: Argument 1 of "__or__" incompatible with supertype "Flag"
549+
550+
[case testMultipleInheritance_MethodDefinitionsCompatibleNoOverride]
551+
from typing import TypeVar, Union
552+
_T = TypeVar('_T')
553+
554+
class Flag:
555+
def __or__(self: _T, other: _T) -> _T: ...
556+
557+
class IntFlag(int, Flag):
558+
pass
559+
[out]
560+
561+
[case testMultipleInheritance_MethodsReturningSelfCompatible]
562+
class A(object):
563+
def x(self) -> 'A':
564+
return self
565+
566+
class B(object):
567+
def x(self) -> 'B':
568+
return self
569+
570+
class C(A, B):
571+
def x(self) -> 'C':
572+
return self
573+
574+
[case testMultipleInheritance_MethodsReturningSelfIncompatible]
575+
class A(object):
576+
def x(self) -> 'A':
577+
return self
578+
579+
class B(object):
580+
def x(self) -> 'B':
581+
return self
582+
583+
class C(A, B): # E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
584+
pass
585+
464586
[case testNestedVariableRefersToSubclassOfAnotherNestedClass]
465587
class Mixin1:
466588
class Meta:

0 commit comments

Comments
 (0)