Skip to content

Commit d920bd7

Browse files
syastrovilevkivskyi
authored andcommitted
Loosen base class attribute compatibility checks when attribute is redefined (#6585)
This eliminates the error `Definition of "Nested" in base class "Base1" is incompatible with definition in base class "Base2"` in the following code, where `Nested` is redefined in `A`: ``` 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'] ``` - 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 9a4cfae commit d920bd7

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

mypy/checker.py

Lines changed: 11 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)
@@ -1867,6 +1870,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[
18671870
# Show only one error per variable
18681871
break
18691872

1873+
direct_bases = lvalue_node.info.direct_base_classes()
1874+
last_immediate_base = direct_bases[-1] if direct_bases else None
1875+
18701876
for base in lvalue_node.info.mro[1:]:
18711877
# Only check __slots__ against the 'object'
18721878
# 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[
18901896
# Only show one error per variable; even if other
18911897
# base classes are also incompatible
18921898
return True
1893-
break
1899+
if base is last_immediate_base:
1900+
# At this point, the attribute was found to be compatible with all
1901+
# immediate parents.
1902+
break
18941903
return False
18951904

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

test-data/unit/check-classes.test

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4025,7 +4025,7 @@ class B(A):
40254025
main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
40264026
main:5: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
40274027

4028-
[case testClassIgnoreType]
4028+
[case testClassIgnoreType_RedefinedAttributeAndGrandparentAttributeTypesNotIgnored]
40294029
class A:
40304030
x = 0
40314031
class B(A):
@@ -4034,6 +4034,15 @@ class C(B):
40344034
x = ''
40354035
[out]
40364036

4037+
[case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren]
4038+
class A:
4039+
x = 0
4040+
class B(A):
4041+
x = '' # type: ignore
4042+
class C(B):
4043+
x = '' # type: ignore
4044+
[out]
4045+
40374046
[case testInvalidMetaclassStructure]
40384047
class X(type): pass
40394048
class Y(type): pass

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

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

0 commit comments

Comments
 (0)