Skip to content

cannot create consistent MRO for multiple generic inheritance #106102

@sanderr

Description

@sanderr

Bug report

When Generic appears twice in a class' inheritance tree, Python may fail to find a consistent MRO. An effort seems to be taken to address this in typing._BaseGenericAlias and typing._GenericAlias. However, when one of the bases is a subscripted generic and the other is a child of a subscripted generic (no longer generic itself) this falls short. I believe the root cause is the fact that _GenericAlias, unlike its parent, only checks for isinstance(b, _BaseGenericAlias and not for issubclass(b, Generic) when considering whether it should skip the Generic base for this MRO entry.

Consider the following example:

from typing import Generic, TypeVar


T = TypeVar("T")


class A(Generic[T]):
    pass


class B(A[int]):
    pass


class Works(B, Generic[T]):
    pass


class WorksToo(Generic[T], A[int]):
    pass


class Broken(Generic[T], B):
    pass

The Works class does not present a problem because Generic[T] appears as the last base. The WorksToo class works because A[int] is recognized by _GenericAlias.__mro_entries__ as another _BaseGenericAlias, therefore Generic is only included as MRO entry for the latter. Broken results in an exception, even though B is semantically equivalent to A[int].

Traceback (most recent call last):
  File "/home/sander/documents/projects/python-sandbox/main.py", line 23, in <module>
    class Broken(Generic[T], B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Generic, B

I managed to get it to run correctly by making the following change to typing.py:

diff --git a/usr/lib/python3.9/typing.py b/typing.py
index d35a2a5..06be18f 100644
--- a/usr/lib/python3.9/typing.py
+++ b/typing.py
@@ -809,7 +809,7 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
                 return ()
             i = bases.index(self)
             for b in bases[i+1:]:
-                if isinstance(b, _BaseGenericAlias) and b is not self:
+                if (isinstance(b, _BaseGenericAlias) or issubclass(b, Generic)) and b is not self:
                     return ()
         return (self.__origin__,)

I am not sufficiently familiar with typing's internals (or even MRO) to be completely confident of this patch, but I believe it to be sound. If this issue gets confirmed I'd be willing to open a pull request with this change.

Your environment

  • CPython versions tested on: 3.9, 3.11
  • Operating system and architecture: x86, Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions