Repository: DetachHead/basedpyright (language: python)

Issue:
`reportUnsafeMultipleInheritance` false positive when diamond
Code sample in [basedpyright playground](https://basedpyright.com/?typeCheckingMode=all&reportUnusedParameter=false&code=MYGwhgzhAEAqD2AHAXAWAFDS9AJgUwDNoB9YgSwDsyAXUgCgjxAIEploA6LjH9USGABlC1OgkRtO3dL35RoAJTIBzABajxkrh14Y5MACLwA7hTrCC1ADSKV6rVywBiLHgBOb%2BG-YBZAK4g1GSIIHjQlKruNGAUwGFkMBTw1NBgICAmeDjQAEZ4wGB%2BjNDUkdAE8OkmlMq5kGH6eDDA8BTUYJTQAAaklDSkXdBe3aQUeMYD0AC2eKXwODClYClTYACe0EkpyrPQBelZ7ACC0HRueIhe1ACqFBBgBHj%2BgcGhAJIUkW7RsXgsvIhvm06EZTBwpp46CwWM5oKCKDYLNZbGpkeIgA)

```python
class Up:
    def __init__(self): ...


class Left(Up): ...


class Right(Up): ...


class Down(Left, Right): ...  #  error: Multiple inheritance is not allowed because the following base classes contain `__init__` or `__new__` methods that may not get called: Up (reportUnsafeMultipleInheritance)

print(Down.mro())  # Down, Left, Right, Up
```



Discussion / hints:
This also comes up when specializing exception classes, e.g.:
```python
def goldilocks() -> None:
    """a complex function with many possible exception states

    Exception classes are arranged into a hierarchy, so similar errors
    can be handled together if desired.

    Raises:
        PorridgeTooHot: when ....
        PorridgeTooCold: when ....
        InvalidBedTypeError: when ...

        Note: all exceptions raised by this function inherit from
              `GoldilocksException`
    """

class GoldilocksException(Exception): ...

class GoldilocksValueError(GoldilocksException, ValueError): ...   # error here

class PorridgeTemperatureError(GoldilocksValueError): ...
class PorridgeTooHot(PorridgeTemperatureError): ...
class PorridgeTooCold(PorridgeTemperatureError): ...

class GoldilocksTypeError(GoldilocksException, TypeError): ...     # error here
class InvalidBedTypeError(GoldilocksTypeError): ...
```

Fix patch:
diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts
index 316b1987d..63ae2bf6c 100644
--- a/packages/pyright-internal/src/analyzer/checker.ts
+++ b/packages/pyright-internal/src/analyzer/checker.ts
@@ -5712,6 +5712,7 @@ export class Checker extends ParseTreeWalker {
         }
         /** if it's a dataclass then no further base classes in the MRO can have constructors */
         let constructorIsSafe = !this._isDataclassWithGeneratedConstructor(classType);
+        const uniqueBaseClasses = new Set<ClassType>();
         const baseClassesWithPossiblyUncalledConstructors: ClassType[] = [];
         const isTypedDict = ClassType.isTypedDictClass(classType);
         const diagAddendum = new DiagnosticAddendum();
@@ -5732,12 +5733,15 @@ export class Checker extends ParseTreeWalker {
                         constructorMethodResult &&
                         constructorMethodResult.classType &&
                         isClass(constructorMethodResult.classType) &&
+                        // prevent the same base class from being checked twice which can happen in diamond inheritance situations
+                        !uniqueBaseClasses.has(constructorMethodResult.classType) &&
                         // synthesized dataclass constructors are safe as the runtime machinery seems to account for multiple inheritance moments
                         !(
                             isFunction(constructorMethodResult.type) &&
                             FunctionType.isSynthesizedMethod(constructorMethodResult.type)
                         )
                     ) {
+                        uniqueBaseClasses.add(constructorMethodResult.classType);
                         if (constructorIsSafe) {
                             constructorIsSafe = false;
                         } else {


Test patch:
diff --git a/packages/pyright-internal/src/tests/samples/multipleInheritance.py b/packages/pyright-internal/src/tests/samples/multipleInheritance.py
index d70efab28..03e58b57d 100644
--- a/packages/pyright-internal/src/tests/samples/multipleInheritance.py
+++ b/packages/pyright-internal/src/tests/samples/multipleInheritance.py
@@ -57,4 +57,16 @@ class U(A, C): # error
 
 @dataclass(init=False)
 class V(A, C): # no error
-    ...
\ No newline at end of file
+    ...
+
+class Up:
+    def __init__(self): ...
+
+
+class Left(Up): ...
+
+
+class Right(Up): ...
+
+
+class Down(Left, Right): ...  # no error
\ No newline at end of file