Backport some recent `Protocol` fixes from 3.12 (#161)

diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index 469c31b..d12c5de 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -1907,6 +1907,63 @@
         with self.assertRaises(TypeError):
             issubclass(D, PNonCall)
 
+    def test_no_weird_caching_with_issubclass_after_isinstance(self):
+        @runtime_checkable
+        class Spam(Protocol):
+            x: int
+
+        class Eggs:
+            def __init__(self) -> None:
+                self.x = 42
+
+        self.assertIsInstance(Eggs(), Spam)
+
+        # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta,
+        # TypeError wouldn't be raised here,
+        # as the cached result of the isinstance() check immediately above
+        # would mean the issubclass() call would short-circuit
+        # before we got to the "raise TypeError" line
+        with self.assertRaises(TypeError):
+            issubclass(Eggs, Spam)
+
+    def test_no_weird_caching_with_issubclass_after_isinstance_2(self):
+        @runtime_checkable
+        class Spam(Protocol):
+            x: int
+
+        class Eggs: ...
+
+        self.assertNotIsInstance(Eggs(), Spam)
+
+        # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta,
+        # TypeError wouldn't be raised here,
+        # as the cached result of the isinstance() check immediately above
+        # would mean the issubclass() call would short-circuit
+        # before we got to the "raise TypeError" line
+        with self.assertRaises(TypeError):
+            issubclass(Eggs, Spam)
+
+    def test_no_weird_caching_with_issubclass_after_isinstance_3(self):
+        @runtime_checkable
+        class Spam(Protocol):
+            x: int
+
+        class Eggs:
+            def __getattr__(self, attr):
+                if attr == "x":
+                    return 42
+                raise AttributeError(attr)
+
+        self.assertNotIsInstance(Eggs(), Spam)
+
+        # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta,
+        # TypeError wouldn't be raised here,
+        # as the cached result of the isinstance() check immediately above
+        # would mean the issubclass() call would short-circuit
+        # before we got to the "raise TypeError" line
+        with self.assertRaises(TypeError):
+            issubclass(Eggs, Spam)
+
     def test_protocols_isinstance(self):
         T = TypeVar('T')
         @runtime_checkable
@@ -2235,10 +2292,10 @@
         class NonP(P):
             x = 1
         class NonPR(PR): pass
-        class C:
+        class C(metaclass=abc.ABCMeta):
             x = 1
-        class D:
-            def meth(self): pass
+        class D(metaclass=abc.ABCMeta):  # noqa: B024
+            def meth(self): pass  # noqa: B027
         self.assertNotIsInstance(C(), NonP)
         self.assertNotIsInstance(D(), NonPR)
         self.assertNotIsSubclass(C, NonP)
@@ -2246,6 +2303,20 @@
         self.assertIsInstance(NonPR(), PR)
         self.assertIsSubclass(NonPR, PR)
 
+        self.assertNotIn("__protocol_attrs__", vars(NonP))
+        self.assertNotIn("__protocol_attrs__", vars(NonPR))
+        self.assertNotIn("__callable_proto_members_only__", vars(NonP))
+        self.assertNotIn("__callable_proto_members_only__", vars(NonPR))
+
+        acceptable_extra_attrs = {
+            '_is_protocol', '_is_runtime_protocol', '__parameters__',
+            '__init__', '__annotations__', '__subclasshook__',
+        }
+        self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs)
+        self.assertLessEqual(
+            vars(NonPR).keys(), vars(D).keys() | acceptable_extra_attrs
+        )
+
     def test_custom_subclasshook(self):
         class P(Protocol):
             x = 1
@@ -2325,6 +2396,48 @@
             with self.assertRaises(TypeError):
                 PR[int, ClassVar]
 
+    if sys.version_info >= (3, 12):
+        exec(textwrap.dedent(
+            """
+            def test_pep695_generic_protocol_callable_members(self):
+                @runtime_checkable
+                class Foo[T](Protocol):
+                    def meth(self, x: T) -> None: ...
+
+                class Bar[T]:
+                    def meth(self, x: T) -> None: ...
+
+                self.assertIsInstance(Bar(), Foo)
+                self.assertIsSubclass(Bar, Foo)
+
+                @runtime_checkable
+                class SupportsTrunc[T](Protocol):
+                    def __trunc__(self) -> T: ...
+
+                self.assertIsInstance(0.0, SupportsTrunc)
+                self.assertIsSubclass(float, SupportsTrunc)
+
+            def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self):
+                @runtime_checkable
+                class Spam[T](Protocol):
+                    x: T
+
+                class Eggs[T]:
+                    def __init__(self, x: T) -> None:
+                        self.x = x
+
+                self.assertIsInstance(Eggs(42), Spam)
+
+                # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta,
+                # TypeError wouldn't be raised here,
+                # as the cached result of the isinstance() check immediately above
+                # would mean the issubclass() call would short-circuit
+                # before we got to the "raise TypeError" line
+                with self.assertRaises(TypeError):
+                    issubclass(Eggs, Spam)
+            """
+        ))
+
     def test_init_called(self):
         T = TypeVar('T')
         class P(Protocol[T]): pass
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index dd12cfb..b74bf13 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -470,6 +470,9 @@
 if sys.version_info >= (3, 9):
     _EXCLUDED_ATTRS.add("__class_getitem__")
 
+if sys.version_info >= (3, 12):
+    _EXCLUDED_ATTRS.add("__type_params__")
+
 _EXCLUDED_ATTRS = frozenset(_EXCLUDED_ATTRS)
 
 
@@ -550,23 +553,37 @@
             raise TypeError('Protocols cannot be instantiated')
 
     class _ProtocolMeta(abc.ABCMeta):
-        # This metaclass is a bit unfortunate and exists only because of the lack
-        # of __instancehook__.
+        # This metaclass is somewhat unfortunate,
+        # but is necessary for several reasons...
         def __init__(cls, *args, **kwargs):
             super().__init__(*args, **kwargs)
-            cls.__protocol_attrs__ = _get_protocol_attrs(cls)
-            # PEP 544 prohibits using issubclass()
-            # with protocols that have non-method members.
-            cls.__callable_proto_members_only__ = all(
-                callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
-            )
+            if getattr(cls, "_is_protocol", False):
+                cls.__protocol_attrs__ = _get_protocol_attrs(cls)
+                # PEP 544 prohibits using issubclass()
+                # with protocols that have non-method members.
+                cls.__callable_proto_members_only__ = all(
+                    callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
+                )
+
+        def __subclasscheck__(cls, other):
+            if (
+                getattr(cls, '_is_protocol', False)
+                and not cls.__callable_proto_members_only__
+                and not _allow_reckless_class_checks(depth=3)
+            ):
+                raise TypeError(
+                    "Protocols with non-method members don't support issubclass()"
+                )
+            return super().__subclasscheck__(other)
 
         def __instancecheck__(cls, instance):
             # We need this method for situations where attributes are
             # assigned in __init__.
-            is_protocol_cls = getattr(cls, "_is_protocol", False)
+            if not getattr(cls, "_is_protocol", False):
+                # i.e., it's a concrete subclass of a protocol
+                return super().__instancecheck__(instance)
+
             if (
-                is_protocol_cls and
                 not getattr(cls, '_is_runtime_protocol', False) and
                 not _allow_reckless_class_checks(depth=2)
             ):
@@ -576,16 +593,15 @@
             if super().__instancecheck__(instance):
                 return True
 
-            if is_protocol_cls:
-                for attr in cls.__protocol_attrs__:
-                    try:
-                        val = inspect.getattr_static(instance, attr)
-                    except AttributeError:
-                        break
-                    if val is None and callable(getattr(cls, attr, None)):
-                        break
-                else:
-                    return True
+            for attr in cls.__protocol_attrs__:
+                try:
+                    val = inspect.getattr_static(instance, attr)
+                except AttributeError:
+                    break
+                if val is None and callable(getattr(cls, attr, None)):
+                    break
+            else:
+                return True
 
             return False
 
@@ -679,11 +695,6 @@
                         return NotImplemented
                     raise TypeError("Instance and class checks can only be used with"
                                     " @runtime protocols")
-                if not cls.__callable_proto_members_only__:
-                    if _allow_reckless_class_checks():
-                        return NotImplemented
-                    raise TypeError("Protocols with non-method members"
-                                    " don't support issubclass()")
                 if not isinstance(other, type):
                     # Same error as for issubclass(1, int)
                     raise TypeError('issubclass() arg 1 must be a class')