Repository: python/mypy
Pull request: Do not block on duplicate base classes
@JelleZijlstra I don't really have a prefence on this issue. I am fine with both blocking and non-blocking errors here.

But, it looks like that making this non-blocking is quite straight-forward.
Closes https://github.com/python/mypy/issues/15349

Problem statement:
“Duplicate base class” blocks checking
The duplicate base class causes the checking blocked. 

example.py
```
class N(type):
    pass

class B(N, metaclass=N):
    pass

class D(B, B):
    pass
```

### The expected behavior：
The error can be ignored and the checking will not be blocked.

### The actual behavior：
```
>> mypy example.py
Desktop/example.py:7: error: Duplicate base class "B"
Found 1 error in 1 file (errors prevented further checking)
```

### Environment
Mypy version: 1.3.0
Python version: 3.11.3
Operating System: Ubuntu 18.04
How to install MyPy: "python3.11 -m pip install mypy"

Hints from issue discussion:
@xiaxinmeng I believe mypy is correct in this case because when class D is constructed, since it inherits two instances of class B, when an attribute or method of class N is invoked from an instance of class D, Python does not know which instance of class N should be called. I believe this issue can be worked around by removing the "metaclass=N" argument from class B
I think that this is a correct thing to do:
1. It is a `TypeError` in runtime:

```python
>>> class A: ...
... 
>>> class B(A, A): ...
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: duplicate base class A
```

So, you app won't work and mypy catches that.

2. It is not clear how to construct `D` type info for us, so we can continue type checking
Can't we make it inherit from Any or ignore the second base? I feel we should strongly avoid blocking errors if possible.
We can still inherit from one of them, I guess. I will take a look and send a PR.

Commit message: Address review

Patch to review:
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..f62f7a094fab 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -4131,6 +4131,20 @@ 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"
+    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"
+[out]
+
 [case testCannotDetermineMro]
 class A: pass
 class B(A): pass


Code review comments:
diff_hunk: @@ -4131,6 +4131,20 @@ class C(str, str):
 [out]
path: test-data/unit/check-classes.test
text: @user1:
nit: maybe we remove testMroSetAfterError? Your new test case seems like a superset