From 66bd2c441277b29b1454e26b2a3454a0dae5fb18 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 5 Jun 2023 10:21:11 +0300 Subject: [PATCH] Do not block on duplicate base classes (#15367) Continue type checking if a class has two or more identical bases. Closes https://github.com/python/mypy/issues/15349 --------- Co-authored-by: hauntsaninja --- mypy/semanal.py | 19 +++++++++++++++---- test-data/unit/check-classes.test | 31 +++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8934dc9321b7..3ca758ad4eb1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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: @@ -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: @@ -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. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 3af20bd1e7ea..c2eddbc597a0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -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