Backport CPython PR 105976 (#252)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7fd5ef1..54c4d1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+# Unreleased
+
+- Fix bug where a `typing_extensions.Protocol` class that had one or more
+  non-callable members would raise `TypeError` when `issubclass()`
+  was called against it, even if it defined a custom `__subclasshook__`
+  method. The correct behaviour -- which has now been restored -- is not to
+  raise `TypeError` in these situations if a custom `__subclasshook__` method
+  is defined. Patch by Alex Waygood (backporting
+  https://github.com/python/cpython/pull/105976).
+
 # Release 4.7.0rc1 (June 21, 2023)
 
 - Add `typing_extensions.get_protocol_members` and
diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index d7e9dc4..c2ab6d7 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -2713,6 +2713,50 @@
         self.assertIsSubclass(OKClass, C)
         self.assertNotIsSubclass(BadClass, C)
 
+    @skipIf(
+        sys.version_info[:4] == (3, 12, 0, 'beta') and sys.version_info[4] < 4,
+        "Early betas of Python 3.12 had a bug"
+    )
+    def test_custom_subclasshook_2(self):
+        @runtime_checkable
+        class HasX(Protocol):
+            # The presence of a non-callable member
+            # would mean issubclass() checks would fail with TypeError
+            # if it weren't for the custom `__subclasshook__` method
+            x = 1
+
+            @classmethod
+            def __subclasshook__(cls, other):
+                return hasattr(other, 'x')
+
+        class Empty: pass
+
+        class ImplementsHasX:
+            x = 1
+
+        self.assertIsInstance(ImplementsHasX(), HasX)
+        self.assertNotIsInstance(Empty(), HasX)
+        self.assertIsSubclass(ImplementsHasX, HasX)
+        self.assertNotIsSubclass(Empty, HasX)
+
+        # isinstance() and issubclass() checks against this still raise TypeError,
+        # despite the presence of the custom __subclasshook__ method,
+        # as it's not decorated with @runtime_checkable
+        class NotRuntimeCheckable(Protocol):
+            @classmethod
+            def __subclasshook__(cls, other):
+                return hasattr(other, 'x')
+
+        must_be_runtime_checkable = (
+            "Instance and class checks can only be used "
+            "with @runtime_checkable protocols"
+        )
+
+        with self.assertRaisesRegex(TypeError, must_be_runtime_checkable):
+            issubclass(object, NotRuntimeCheckable)
+        with self.assertRaisesRegex(TypeError, must_be_runtime_checkable):
+            isinstance(object(), NotRuntimeCheckable)
+
     @skip_if_py312b1
     def test_issubclass_fails_correctly(self):
         @runtime_checkable
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index c37926d..b77c1fd 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -644,14 +644,17 @@
         def __subclasscheck__(cls, other):
             if cls is Protocol:
                 return type.__subclasscheck__(cls, other)
-            if not isinstance(other, type):
-                # Same error message as for issubclass(1, int).
-                raise TypeError('issubclass() arg 1 must be a class')
             if (
                 getattr(cls, '_is_protocol', False)
                 and not _allow_reckless_class_checks()
             ):
-                if not cls.__callable_proto_members_only__:
+                if not isinstance(other, type):
+                    # Same error message as for issubclass(1, int).
+                    raise TypeError('issubclass() arg 1 must be a class')
+                if (
+                    not cls.__callable_proto_members_only__
+                    and cls.__dict__.get("__subclasshook__") is _proto_hook
+                ):
                     raise TypeError(
                         "Protocols with non-method members don't support issubclass()"
                     )
@@ -752,12 +755,8 @@
                 if '__subclasshook__' not in cls.__dict__:
                     cls.__subclasshook__ = _proto_hook
 
-                # We have nothing more to do for non-protocols...
-                if not cls._is_protocol:
-                    return
-
-                # ... otherwise prohibit instantiation.
-                if cls.__init__ is Protocol.__init__:
+                # Prohibit instantiation for protocol classes
+                if cls._is_protocol and cls.__init__ is Protocol.__init__:
                     cls.__init__ = _no_init
 
     else:
@@ -847,12 +846,8 @@
                 if '__subclasshook__' not in cls.__dict__:
                     cls.__subclasshook__ = _proto_hook
 
-                # We have nothing more to do for non-protocols.
-                if not cls._is_protocol:
-                    return
-
-                # Prohibit instantiation
-                if cls.__init__ is Protocol.__init__:
+                # Prohibit instantiation for protocol classes
+                if cls._is_protocol and cls.__init__ is Protocol.__init__:
                     cls.__init__ = _no_init