From b173d485724d0f1b6aa63af95b8f7608f72762c3 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Jun 2023 17:16:14 +0300 Subject: [PATCH 1/4] Do not block on duplicate base classes --- mypy/semanal.py | 2 +- test-data/unit/check-classes.test | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8934dc9321b7..e6b6feb5b131 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2300,7 +2300,7 @@ def verify_base_classes(self, defn: ClassDef) -> bool: cycle = True dup = find_duplicate(info.direct_base_classes()) if dup: - self.fail(f'Duplicate base class "{dup.name}"', defn, blocker=True) + self.fail(f'Duplicate base class "{dup.name}"', defn) return False return not cycle diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 3af20bd1e7ea..1d6a47b522e8 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4131,6 +4131,18 @@ class C(str, str): [out] main:1: error: Duplicate base class "str" +[case testDupBaseClassesIsNotBlocking] +class A: + def method(self) -> str: ... + +class B(A, A): # E: Duplicate base class "A" + ... + +b: B +# We use dummy type info, so no real attrs will be there: +b.method() # E: "B" has no attribute "method" +[out] + [case testCannotDetermineMro] class A: pass class B(A): pass From 06a02caab80d23462af95b7cf71969af313a192b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Jun 2023 19:46:25 +0300 Subject: [PATCH 2/4] Address review --- mypy/semanal.py | 17 ++++++++++++++--- test-data/unit/check-classes.test | 8 +++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index e6b6feb5b131..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,11 +2307,13 @@ 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()) + 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 False - return not cycle + 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).""" diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1d6a47b522e8..f62f7a094fab 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4136,11 +4136,13 @@ class A: def method(self) -> str: ... class B(A, A): # E: Duplicate base class "A" - ... + attr: int b: B -# We use dummy type info, so no real attrs will be there: -b.method() # E: "B" has no attribute "method" + +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" [out] [case testCannotDetermineMro] From cbec114d1c1c12a5b8eed93e88467a6b70660387 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Jun 2023 23:47:21 +0300 Subject: [PATCH 3/4] Address review --- test-data/unit/check-classes.test | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f62f7a094fab..e5eeaf4a5b7d 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4124,13 +4124,6 @@ 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 testDupBaseClassesIsNotBlocking] class A: def method(self) -> str: ... From e14021eedfed85c087687e3db6fc3f72f38719da Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 4 Jun 2023 14:03:50 -0700 Subject: [PATCH 4/4] generic test --- test-data/unit/check-classes.test | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e5eeaf4a5b7d..c2eddbc597a0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4124,7 +4124,7 @@ 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 testDupBaseClassesIsNotBlocking] +[case testDupBaseClasses] class A: def method(self) -> str: ... @@ -4136,7 +4136,19 @@ 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" -[out] + +[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