Allow typing_extensions.Protocol and typing.Protocol to mix (#237)

Fixes #236

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2df212b..191a583 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,9 @@
 - Allow `Protocol` classes to inherit from `typing_extensions.Buffer` or
   `collections.abc.Buffer`. Patch by Alex Waygood (backporting
   https://github.com/python/cpython/pull/104827, by Jelle Zijlstra).
+- Allow classes to inherit from both `typing.Protocol` and `typing_extensions.Protocol`
+  simultaneously. Since v4.6.0, this caused `TypeError` to be raised due to a
+  metaclass conflict. Patch by Alex Waygood.
 
 # Release 4.6.3 (June 1, 2023)
 
diff --git a/doc/index.rst b/doc/index.rst
index 8d1f9a8..82109c6 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -282,6 +282,12 @@
       Backported changes to runtime-checkable protocols from Python 3.12,
       including :pr-cpy:`103034` and :pr-cpy:`26067`.
 
+   .. versionchanged:: 4.7.0
+
+      Classes can now inherit from both :py:class:`typing.Protocol` and
+      ``typing_extensions.Protocol`` simultaneously. Previously, this led to
+      :py:exc:`TypeError` being raised due to a metaclass conflict.
+
 .. data:: Required
 
    See :py:data:`typing.Required` and :pep:`655`. In ``typing`` since 3.11.
diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index 102f0c1..bd1aa0f 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -1799,6 +1799,34 @@
         self.assertIsInstance(Bar(), Foo)
         self.assertNotIsInstance(object(), Foo)
 
+    @skipUnless(
+        hasattr(typing, "Protocol"),
+        "Test is only relevant if typing.Protocol exists"
+    )
+    def test_typing_Protocol_and_extensions_Protocol_can_mix(self):
+        class TypingProto(typing.Protocol):
+            x: int
+
+        class ExtensionsProto(Protocol):
+            y: int
+
+        class SubProto(TypingProto, ExtensionsProto, typing.Protocol):
+            z: int
+
+        class SubProto2(TypingProto, ExtensionsProto, Protocol):
+            z: int
+
+        class SubProto3(ExtensionsProto, TypingProto, typing.Protocol):
+            z: int
+
+        class SubProto4(ExtensionsProto, TypingProto, Protocol):
+            z: int
+
+        class Concrete(SubProto): pass
+        class Concrete2(SubProto2): pass
+        class Concrete3(SubProto3): pass
+        class Concrete4(SubProto4): pass
+
     def test_no_instantiation(self):
         class P(Protocol): pass
         with self.assertRaises(TypeError):
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index 449ea5e..e6c1ca8 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -596,30 +596,54 @@
         if type(self)._is_protocol:
             raise TypeError('Protocols cannot be instantiated')
 
-    class _ProtocolMeta(abc.ABCMeta):
+    if sys.version_info >= (3, 8):
+        # Inheriting from typing._ProtocolMeta isn't actually desirable,
+        # but is necessary to allow typing.Protocol and typing_extensions.Protocol
+        # to mix without getting TypeErrors about "metaclass conflict"
+        _typing_Protocol = typing.Protocol
+        _ProtocolMetaBase = type(_typing_Protocol)
+
+        def _is_protocol(cls):
+            return (
+                isinstance(cls, type)
+                and issubclass(cls, typing.Generic)
+                and getattr(cls, "_is_protocol", False)
+            )
+    else:
+        _typing_Protocol = _marker
+        _ProtocolMetaBase = abc.ABCMeta
+
+        def _is_protocol(cls):
+            return (
+                isinstance(cls, _ProtocolMeta)
+                and getattr(cls, "_is_protocol", False)
+            )
+
+    class _ProtocolMeta(_ProtocolMetaBase):
         # This metaclass is somewhat unfortunate,
         # but is necessary for several reasons...
+        #
+        # NOTE: DO NOT call super() in any methods in this class
+        # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
+        # and those are slow
         def __new__(mcls, name, bases, namespace, **kwargs):
             if name == "Protocol" and len(bases) < 2:
                 pass
-            elif Protocol in bases:
+            elif {Protocol, _typing_Protocol} & set(bases):
                 for base in bases:
                     if not (
                         base in {object, typing.Generic}
                         or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
-                        or (
-                            isinstance(base, _ProtocolMeta)
-                            and getattr(base, "_is_protocol", False)
-                        )
+                        or _is_protocol(base)
                     ):
                         raise TypeError(
                             f"Protocols can only inherit from other protocols, "
                             f"got {base!r}"
                         )
-            return super().__new__(mcls, name, bases, namespace, **kwargs)
+            return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)
 
         def __init__(cls, *args, **kwargs):
-            super().__init__(*args, **kwargs)
+            abc.ABCMeta.__init__(cls, *args, **kwargs)
             if getattr(cls, "_is_protocol", False):
                 cls.__protocol_attrs__ = _get_protocol_attrs(cls)
                 # PEP 544 prohibits using issubclass()
@@ -647,7 +671,7 @@
                         "Instance and class checks can only be used with "
                         "@runtime_checkable protocols"
                     )
-            return super().__subclasscheck__(other)
+            return abc.ABCMeta.__subclasscheck__(cls, other)
 
         def __instancecheck__(cls, instance):
             # We need this method for situations where attributes are
@@ -656,7 +680,7 @@
                 return type.__instancecheck__(cls, instance)
             if not getattr(cls, "_is_protocol", False):
                 # i.e., it's a concrete subclass of a protocol
-                return super().__instancecheck__(instance)
+                return abc.ABCMeta.__instancecheck__(cls, instance)
 
             if (
                 not getattr(cls, '_is_runtime_protocol', False) and
@@ -665,7 +689,7 @@
                 raise TypeError("Instance and class checks can only be used with"
                                 " @runtime_checkable protocols")
 
-            if super().__instancecheck__(instance):
+            if abc.ABCMeta.__instancecheck__(cls, instance):
                 return True
 
             for attr in cls.__protocol_attrs__:
@@ -684,7 +708,7 @@
             # Hack so that typing.Generic.__class_getitem__
             # treats typing_extensions.Protocol
             # as equivalent to typing.Protocol on Python 3.8+
-            if super().__eq__(other) is True:
+            if abc.ABCMeta.__eq__(cls, other) is True:
                 return True
             return (
                 cls is Protocol and other is getattr(typing, "Protocol", object())