Skip to content

Commit d86b1e5

Browse files
Fix attribute type resolution with multiple inheritance (#18415)
Fixes #18268. Fixes #9319. Fixes #14279. Fixes #9031. Supersedes #18270 as requested by @ilevkivskyi. This PR introduces two changes: * Add missing `map_type_from_supertype` when checking generic attributes * Only compare the first base defining a name to all following in MRO - others are not necessarily pairwise compatible. --------- Co-authored-by: Shantanu <[email protected]>
1 parent 106f714 commit d86b1e5

File tree

3 files changed

+75
-11
lines changed

3 files changed

+75
-11
lines changed

mypy/checker.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -2733,19 +2733,20 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
27332733
return
27342734
# Verify that inherited attributes are compatible.
27352735
mro = typ.mro[1:]
2736-
for i, base in enumerate(mro):
2736+
all_names = {name for base in mro for name in base.names}
2737+
for name in sorted(all_names - typ.names.keys()):
2738+
# Sort for reproducible message order.
27372739
# Attributes defined in both the type and base are skipped.
27382740
# Normal checks for attribute compatibility should catch any problems elsewhere.
2739-
non_overridden_attrs = base.names.keys() - typ.names.keys()
2740-
for name in non_overridden_attrs:
2741-
if is_private(name):
2742-
continue
2743-
for base2 in mro[i + 1 :]:
2744-
# We only need to check compatibility of attributes from classes not
2745-
# in a subclass relationship. For subclasses, normal (single inheritance)
2746-
# checks suffice (these are implemented elsewhere).
2747-
if name in base2.names and base2 not in base.mro:
2748-
self.check_compatibility(name, base, base2, typ)
2741+
if is_private(name):
2742+
continue
2743+
# Compare the first base defining a name with the rest.
2744+
# Remaining bases may not be pairwise compatible as the first base provides
2745+
# the used definition.
2746+
i, base = next((i, base) for i, base in enumerate(mro) if name in base.names)
2747+
for base2 in mro[i + 1 :]:
2748+
if name in base2.names and base2 not in base.mro:
2749+
self.check_compatibility(name, base, base2, typ)
27492750

27502751
def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None:
27512752
if sym.type is not None:
@@ -2826,8 +2827,10 @@ class C(B, A[int]): ... # this is unsafe because...
28262827
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
28272828
elif first_type and second_type:
28282829
if isinstance(first.node, Var):
2830+
first_type = get_proper_type(map_type_from_supertype(first_type, ctx, base1))
28292831
first_type = expand_self_type(first.node, first_type, fill_typevars(ctx))
28302832
if isinstance(second.node, Var):
2833+
second_type = get_proper_type(map_type_from_supertype(second_type, ctx, base2))
28312834
second_type = expand_self_type(second.node, second_type, fill_typevars(ctx))
28322835
ok = is_equivalent(first_type, second_type)
28332836
if not ok:

test-data/unit/check-generic-subtyping.test

+35
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,38 @@ class F(E[T_co], Generic[T_co]): ... # E: Variance of TypeVar "T_co" incompatib
10651065

10661066
class G(Generic[T]): ...
10671067
class H(G[T_contra], Generic[T_contra]): ... # E: Variance of TypeVar "T_contra" incompatible with variance in parent type
1068+
1069+
[case testMultipleInheritanceCompatibleTypeVar]
1070+
from typing import Generic, TypeVar
1071+
1072+
T = TypeVar("T")
1073+
U = TypeVar("U")
1074+
1075+
class A(Generic[T]):
1076+
x: T
1077+
def fn(self, t: T) -> None: ...
1078+
1079+
class A2(A[T]):
1080+
y: str
1081+
z: str
1082+
1083+
class B(Generic[T]):
1084+
x: T
1085+
def fn(self, t: T) -> None: ...
1086+
1087+
class C1(A2[str], B[str]): pass
1088+
class C2(A2[str], B[int]): pass # E: Definition of "fn" in base class "A" is incompatible with definition in base class "B" \
1089+
# E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
1090+
class C3(A2[T], B[T]): pass
1091+
class C4(A2[U], B[U]): pass
1092+
class C5(A2[U], B[T]): pass # E: Definition of "fn" in base class "A" is incompatible with definition in base class "B" \
1093+
# E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
1094+
1095+
class D1(A[str], B[str]): pass
1096+
class D2(A[str], B[int]): pass # E: Definition of "fn" in base class "A" is incompatible with definition in base class "B" \
1097+
# E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
1098+
class D3(A[T], B[T]): pass
1099+
class D4(A[U], B[U]): pass
1100+
class D5(A[U], B[T]): pass # E: Definition of "fn" in base class "A" is incompatible with definition in base class "B" \
1101+
# E: Definition of "x" in base class "A" is incompatible with definition in base class "B"
1102+
[builtins fixtures/tuple.pyi]

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

+26
Original file line numberDiff line numberDiff line change
@@ -706,3 +706,29 @@ class C34(B3, B4): ...
706706
class C41(B4, B1): ...
707707
class C42(B4, B2): ...
708708
class C43(B4, B3): ...
709+
710+
[case testMultipleInheritanceExplicitDiamondResolution]
711+
# Adapted from #14279
712+
class A:
713+
class M:
714+
pass
715+
716+
class B0(A):
717+
class M(A.M):
718+
pass
719+
720+
class B1(A):
721+
class M(A.M):
722+
pass
723+
724+
class C(B0,B1):
725+
class M(B0.M, B1.M):
726+
pass
727+
728+
class D0(B0):
729+
pass
730+
class D1(B1):
731+
pass
732+
733+
class D(D0,D1,C):
734+
pass

0 commit comments

Comments
 (0)