Skip to content

Commit

Permalink
Do not block on duplicate base classes (#15367)
Browse files Browse the repository at this point in the history
Continue type checking if a class has two or more identical bases.
Closes #15349

---------

Co-authored-by: hauntsaninja <[email protected]>
  • Loading branch information
sobolevn and hauntsaninja authored Jun 5, 2023
1 parent 21c5439 commit 66bd2c4
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 10 deletions.
19 changes: 15 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2189,6 +2189,10 @@ def configure_base_classes(
if not self.verify_base_classes(defn):
self.set_dummy_mro(defn.info)
return
if not self.verify_duplicate_base_classes(defn):
# We don't want to block the typechecking process,
# so, we just insert `Any` as the base class and show an error.
self.set_any_mro(defn.info)
self.calculate_class_mro(defn, self.object_type)

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

def set_any_mro(self, info: TypeInfo) -> None:
# Give it an MRO consisting direct `Any` subclass.
info.fallback_to_any = True
info.mro = [info, self.object_type().type]

def calculate_class_mro(
self, defn: ClassDef, obj_type: Callable[[], Instance] | None = None
) -> None:
Expand Down Expand Up @@ -2298,12 +2307,14 @@ def verify_base_classes(self, defn: ClassDef) -> bool:
if self.is_base_class(info, baseinfo):
self.fail("Cycle in inheritance hierarchy", defn)
cycle = True
dup = find_duplicate(info.direct_base_classes())
if dup:
self.fail(f'Duplicate base class "{dup.name}"', defn, blocker=True)
return False
return not cycle

def verify_duplicate_base_classes(self, defn: ClassDef) -> bool:
dup = find_duplicate(defn.info.direct_base_classes())
if dup:
self.fail(f'Duplicate base class "{dup.name}"', defn)
return not dup

def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
"""Determine if t is a base class of s (but do not use mro)."""
# Search the base class graph for t, starting from s.
Expand Down
31 changes: 25 additions & 6 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -4124,12 +4124,31 @@ int.__eq__(3, 4)
main:33: error: Too few arguments for "__eq__" of "int"
main:33: error: Unsupported operand types for == ("int" and "Type[int]")

[case testMroSetAfterError]
class C(str, str):
foo = 0
bar = foo
[out]
main:1: error: Duplicate base class "str"
[case testDupBaseClasses]
class A:
def method(self) -> str: ...

class B(A, A): # E: Duplicate base class "A"
attr: int

b: B

reveal_type(b.method()) # N: Revealed type is "Any"
reveal_type(b.missing()) # N: Revealed type is "Any"
reveal_type(b.attr) # N: Revealed type is "builtins.int"

[case testDupBaseClassesGeneric]
from typing import Generic, TypeVar

T = TypeVar('T')
class A(Generic[T]):
def method(self) -> T: ...

class B(A[int], A[str]): # E: Duplicate base class "A"
attr: int

reveal_type(B().method()) # N: Revealed type is "Any"
reveal_type(B().attr) # N: Revealed type is "builtins.int"

[case testCannotDetermineMro]
class A: pass
Expand Down

0 comments on commit 66bd2c4

Please sign in to comment.