Skip to content

Commit 66bd2c4

Browse files
Do not block on duplicate base classes (#15367)
Continue type checking if a class has two or more identical bases. Closes #15349 --------- Co-authored-by: hauntsaninja <[email protected]>
1 parent 21c5439 commit 66bd2c4

File tree

2 files changed

+40
-10
lines changed

2 files changed

+40
-10
lines changed

mypy/semanal.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,10 @@ def configure_base_classes(
21892189
if not self.verify_base_classes(defn):
21902190
self.set_dummy_mro(defn.info)
21912191
return
2192+
if not self.verify_duplicate_base_classes(defn):
2193+
# We don't want to block the typechecking process,
2194+
# so, we just insert `Any` as the base class and show an error.
2195+
self.set_any_mro(defn.info)
21922196
self.calculate_class_mro(defn, self.object_type)
21932197

21942198
def configure_tuple_base_class(self, defn: ClassDef, base: TupleType) -> Instance:
@@ -2218,6 +2222,11 @@ def set_dummy_mro(self, info: TypeInfo) -> None:
22182222
info.mro = [info, self.object_type().type]
22192223
info.bad_mro = True
22202224

2225+
def set_any_mro(self, info: TypeInfo) -> None:
2226+
# Give it an MRO consisting direct `Any` subclass.
2227+
info.fallback_to_any = True
2228+
info.mro = [info, self.object_type().type]
2229+
22212230
def calculate_class_mro(
22222231
self, defn: ClassDef, obj_type: Callable[[], Instance] | None = None
22232232
) -> None:
@@ -2298,12 +2307,14 @@ def verify_base_classes(self, defn: ClassDef) -> bool:
22982307
if self.is_base_class(info, baseinfo):
22992308
self.fail("Cycle in inheritance hierarchy", defn)
23002309
cycle = True
2301-
dup = find_duplicate(info.direct_base_classes())
2302-
if dup:
2303-
self.fail(f'Duplicate base class "{dup.name}"', defn, blocker=True)
2304-
return False
23052310
return not cycle
23062311

2312+
def verify_duplicate_base_classes(self, defn: ClassDef) -> bool:
2313+
dup = find_duplicate(defn.info.direct_base_classes())
2314+
if dup:
2315+
self.fail(f'Duplicate base class "{dup.name}"', defn)
2316+
return not dup
2317+
23072318
def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
23082319
"""Determine if t is a base class of s (but do not use mro)."""
23092320
# Search the base class graph for t, starting from s.

test-data/unit/check-classes.test

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4124,12 +4124,31 @@ int.__eq__(3, 4)
41244124
main:33: error: Too few arguments for "__eq__" of "int"
41254125
main:33: error: Unsupported operand types for == ("int" and "Type[int]")
41264126

4127-
[case testMroSetAfterError]
4128-
class C(str, str):
4129-
foo = 0
4130-
bar = foo
4131-
[out]
4132-
main:1: error: Duplicate base class "str"
4127+
[case testDupBaseClasses]
4128+
class A:
4129+
def method(self) -> str: ...
4130+
4131+
class B(A, A): # E: Duplicate base class "A"
4132+
attr: int
4133+
4134+
b: B
4135+
4136+
reveal_type(b.method()) # N: Revealed type is "Any"
4137+
reveal_type(b.missing()) # N: Revealed type is "Any"
4138+
reveal_type(b.attr) # N: Revealed type is "builtins.int"
4139+
4140+
[case testDupBaseClassesGeneric]
4141+
from typing import Generic, TypeVar
4142+
4143+
T = TypeVar('T')
4144+
class A(Generic[T]):
4145+
def method(self) -> T: ...
4146+
4147+
class B(A[int], A[str]): # E: Duplicate base class "A"
4148+
attr: int
4149+
4150+
reveal_type(B().method()) # N: Revealed type is "Any"
4151+
reveal_type(B().attr) # N: Revealed type is "builtins.int"
41334152

41344153
[case testCannotDetermineMro]
41354154
class A: pass

0 commit comments

Comments
 (0)