Drop support for Python 3.7 (#267)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78610e2..e221022 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,8 +42,6 @@
         # For available versions, see:
         # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
         python-version:
-          - "3.7"
-          - "3.7.1"
           - "3.8"
           - "3.8.0"
           - "3.9"
@@ -53,7 +51,6 @@
           - "3.11"
           - "3.11.0"
           - "3.12"
-          - "pypy3.7"
           - "pypy3.8"
           - "pypy3.9"
           - "pypy3.10"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e490c5..f67da87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# Release 4.8.0 (???)
+
+- Drop support for Python 3.7 (including PyPy-3.7). Patch by Alex Waygood.
+
 # Release 4.7.1 (July 2, 2023)
 
 - Fix support for `TypedDict`, `NamedTuple` and `is_protocol` on PyPy-3.7 and
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9d07313..a118a40 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,8 +18,6 @@
 standard library. Such features should already be specified in a PEP or merged into
 CPython's `main` branch.
 
-`typing_extensions` supports Python versions 3.7 and up.
-
 # Versioning scheme
 
 Starting with version 4.0.0, `typing_extensions` uses
diff --git a/README.md b/README.md
index efd3a82..1eddb2a 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,6 @@
 on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`,
 where `x.y` is the first version that includes all features you need.
 
-`typing_extensions` supports Python versions 3.7 and higher.
-
 ## Included items
 
 See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a
diff --git a/doc/index.rst b/doc/index.rst
index 5fd2b2e..7e68e27 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -135,7 +135,7 @@
 Python version support
 ----------------------
 
-``typing_extensions`` currently supports Python versions 3.7 and higher. In the future,
+``typing_extensions`` currently supports Python versions 3.8 and higher. In the future,
 support for older Python versions will be dropped some time after that version
 reaches end of life.
 
diff --git a/pyproject.toml b/pyproject.toml
index 736e1e4..6c36b14 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,9 +7,9 @@
 [project]
 name = "typing_extensions"
 version = "4.7.1"
-description = "Backported and Experimental Type Hints for Python 3.7+"
+description = "Backported and Experimental Type Hints for Python 3.8+"
 readme = "README.md"
-requires-python = ">=3.7"
+requires-python = ">=3.8"
 license = { file = "LICENSE" }
 keywords = [
     "annotations",
@@ -34,7 +34,6 @@
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index c2ab6d7..78fe0c0 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -42,7 +42,6 @@
 
 # Flags used to mark tests that only apply after a specific
 # version of the typing module.
-TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0)
 TYPING_3_9_0 = sys.version_info[:3] >= (3, 9, 0)
 TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0)
 
@@ -52,10 +51,6 @@
 # 3.12 changes the representation of Unpack[] (PEP 692)
 TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0)
 
-only_with_typing_Protocol = skipUnless(
-    hasattr(typing, "Protocol"), "Only relevant when typing.Protocol exists"
-)
-
 # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
 # versions, but not all
 HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters
@@ -246,13 +241,7 @@
         def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ...
 
         expected = {'arg': NoReturn, 'return': NoReturn}
-        targets = [some]
-
-        # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772
-        # wasn't applied yet and NoReturn fails _type_check.
-        if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)):
-            targets.append(some_str)
-        for target in targets:
+        for target in some, some_str:
             with self.subTest(target=target):
                 self.assertEqual(gth(target), expected)
 
@@ -595,15 +584,11 @@
             Final[int][str]
 
     def test_repr(self):
-        if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
-            mod_name = 'typing'
-        else:
-            mod_name = 'typing_extensions'
-        self.assertEqual(repr(Final), mod_name + '.Final')
+        self.assertEqual(repr(Final), 'typing.Final')
         cv = Final[int]
-        self.assertEqual(repr(cv), mod_name + '.Final[int]')
+        self.assertEqual(repr(cv), 'typing.Final[int]')
         cv = Final[Employee]
-        self.assertEqual(repr(cv), mod_name + f'.Final[{__name__}.Employee]')
+        self.assertEqual(repr(cv), f'typing.Final[{__name__}.Employee]')
 
     def test_cannot_subclass(self):
         with self.assertRaises(TypeError):
@@ -1771,7 +1756,6 @@
         self.assertNotIsInstance(D(), E)
         self.assertNotIsInstance(E(), D)
 
-    @only_with_typing_Protocol
     def test_runtimecheckable_on_typing_dot_Protocol(self):
         @runtime_checkable
         class Foo(typing.Protocol):
@@ -1784,7 +1768,6 @@
         self.assertIsInstance(Bar(), Foo)
         self.assertNotIsInstance(object(), Foo)
 
-    @only_with_typing_Protocol
     def test_typing_dot_runtimecheckable_on_Protocol(self):
         @typing.runtime_checkable
         class Foo(Protocol):
@@ -1797,7 +1780,6 @@
         self.assertIsInstance(Bar(), Foo)
         self.assertNotIsInstance(object(), Foo)
 
-    @only_with_typing_Protocol
     def test_typing_Protocol_and_extensions_Protocol_can_mix(self):
         class TypingProto(typing.Protocol):
             x: int
@@ -3173,7 +3155,6 @@
         with self.assertRaisesRegex(TypeError, "not a Protocol"):
             get_protocol_members(ConcreteInherit())
 
-    @only_with_typing_Protocol
     def test_get_protocol_members_typing(self):
         with self.assertRaisesRegex(TypeError, "not a Protocol"):
             get_protocol_members(typing.Protocol)
@@ -3222,7 +3203,6 @@
         # Protocol is not itself a protocol
         self.assertFalse(is_protocol(Protocol))
 
-    @only_with_typing_Protocol
     def test_is_protocol_with_typing(self):
         self.assertFalse(is_protocol(typing.Protocol))
 
@@ -3681,7 +3661,6 @@
         if hasattr(typing, "TypedDict"):
             self.assertIs(is_typeddict(typing.TypedDict), False)
 
-    @skipUnless(TYPING_3_8_0, "Python 3.8+ required")
     def test_is_typeddict_against_typeddict_from_typing(self):
         Point = typing.TypedDict('Point', {'x': int, 'y': int})
 
@@ -3844,7 +3823,7 @@
     def test_non_generic_subscript(self):
         # For backward compatibility, subscription works
         # on arbitrary TypedDict types.
-        # (But we don't attempt to backport this misfeature onto 3.7 and 3.8.)
+        # (But we don't attempt to backport this misfeature onto 3.8.)
         class TD(TypedDict):
             a: T
         A = TD[int]
@@ -4034,17 +4013,8 @@
             classvar: Annotated[ClassVar[int], "a decoration"] = 4
             const: Annotated[Final[int], "Const"] = 4
 
-        if sys.version_info[:2] >= (3, 7):
-            self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int])
-            self.assertEqual(get_type_hints(C, globals())["const"], Final[int])
-        else:
-            self.assertEqual(
-                get_type_hints(C, globals())["classvar"],
-                Annotated[ClassVar[int], "a decoration"]
-            )
-            self.assertEqual(
-                get_type_hints(C, globals())["const"], Annotated[Final[int], "Const"]
-            )
+        self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int])
+        self.assertEqual(get_type_hints(C, globals())["const"], Final[int])
 
     def test_cannot_subclass(self):
         with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"):
@@ -5069,11 +5039,8 @@
             'dataclass_transform',
             'overload',
             'ParamSpec',
-            'Text',
             'TypeVar',
             'TypeVarTuple',
-            'TYPE_CHECKING',
-            'Final',
             'get_type_hints',
         }
         if sys.version_info < (3, 10):
@@ -5189,13 +5156,6 @@
                 x: int = 3
                 y: int
 
-    @skipUnless(
-        (
-            TYPING_3_8_0
-            or hasattr(CoolEmployeeWithDefault, '_field_defaults')
-        ),
-        '"_field_defaults" attribute was added in a micro version of 3.7'
-    )
     def test_field_defaults(self):
         self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
 
@@ -5296,7 +5256,7 @@
         self.assertEqual(a, (1, [2]))
 
     @skipIf(TYPING_3_9_0, "Test isn't relevant to 3.9+")
-    def test_non_generic_subscript_error_message_py38_minus(self):
+    def test_non_generic_subscript_error_message_py38(self):
         class Group(NamedTuple):
             key: T
             group: List[T]
@@ -5389,10 +5349,7 @@
                 self.assertEqual(struct._fields, ())
                 self.assertEqual(struct.__annotations__, {})
                 self.assertIsInstance(struct(), struct)
-                # Attribute was added in a micro version of 3.7
-                # and is tested more fully elsewhere
-                if hasattr(struct, "_field_defaults"):
-                    self.assertEqual(struct._field_defaults, {})
+                self.assertEqual(struct._field_defaults, {})
 
     def test_namedtuple_errors(self):
         with self.assertRaises(TypeError):
@@ -5429,15 +5386,6 @@
     def test_docstring(self):
         self.assertIsInstance(NamedTuple.__doc__, str)
 
-    @skipUnless(TYPING_3_8_0, "NamedTuple had a bad signature on <=3.7")
-    def test_signature_is_same_as_typing_NamedTuple(self):
-        self.assertEqual(inspect.signature(NamedTuple), inspect.signature(typing.NamedTuple))
-
-    @skipIf(TYPING_3_8_0, "tests are only relevant to <=3.7")
-    def test_signature_on_37(self):
-        self.assertIsInstance(inspect.signature(NamedTuple), inspect.Signature)
-        self.assertFalse(hasattr(NamedTuple, "__text_signature__"))
-
     @skipUnless(TYPING_3_9_0, "NamedTuple was a class on 3.8 and lower")
     def test_same_as_typing_NamedTuple_39_plus(self):
         self.assertEqual(
@@ -5592,7 +5540,7 @@
                                     r"Bound must be a type\. Got \(1, 2\)\."):
             TypeVar('X', bound=(1, 2))
 
-    # Technically we could run it on later versions of 3.7 and 3.8,
+    # Technically we could run it on later versions of 3.8,
     # but that's not worth the effort.
     @skipUnless(TYPING_3_9_0, "Fix was not backported")
     def test_missing__name__(self):
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index 901f3b9..0978c5f 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -248,32 +248,7 @@
         return 'typing_extensions.' + self._name
 
 
-# On older versions of typing there is an internal class named "Final".
-# 3.8+
-if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
-    Final = typing.Final
-# 3.7
-else:
-    class _FinalForm(_ExtensionsSpecialForm, _root=True):
-        def __getitem__(self, parameters):
-            item = typing._type_check(parameters,
-                                      f'{self._name} accepts only a single type.')
-            return typing._GenericAlias(self, (item,))
-
-    Final = _FinalForm('Final',
-                       doc="""A special typing construct to indicate that a name
-                       cannot be re-assigned or overridden in a subclass.
-                       For example:
-
-                           MAX_SIZE: Final = 9000
-                           MAX_SIZE += 1  # Error reported by type checker
-
-                           class Connection:
-                               TIMEOUT: Final[int] = 10
-                           class FastConnector(Connection):
-                               TIMEOUT = 1  # Error reported by type checker
-
-                       There is no runtime checking of these properties.""")
+Final = typing.Final
 
 if sys.version_info >= (3, 11):
     final = typing.final
@@ -465,8 +440,6 @@
 
 # Various ABCs mimicking those in collections.abc.
 # A few are simply re-exported for completeness.
-
-
 Awaitable = typing.Awaitable
 Coroutine = typing.Coroutine
 AsyncIterable = typing.AsyncIterable
@@ -475,14 +448,7 @@
 ContextManager = typing.ContextManager
 AsyncContextManager = typing.AsyncContextManager
 DefaultDict = typing.DefaultDict
-
-# 3.7.2+
-if hasattr(typing, 'OrderedDict'):
-    OrderedDict = typing.OrderedDict
-# 3.7.0-3.7.2
-else:
-    OrderedDict = typing._alias(collections.OrderedDict, (KT, VT))
-
+OrderedDict = typing.OrderedDict
 Counter = typing.Counter
 ChainMap = typing.ChainMap
 AsyncGenerator = typing.AsyncGenerator
@@ -508,12 +474,6 @@
     "__protocol_attrs__", "__callable_proto_members_only__",
 }
 
-if sys.version_info < (3, 8):
-    _EXCLUDED_ATTRS |= {
-        "_gorg", "__next_in_mro__", "__extra__", "__tree_hash__", "__args__",
-        "__origin__"
-    }
-
 if sys.version_info >= (3, 9):
     _EXCLUDED_ATTRS.add("__class_getitem__")
 
@@ -535,46 +495,6 @@
     return attrs
 
 
-def _maybe_adjust_parameters(cls):
-    """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__.
-
-    The contents of this function are very similar
-    to logic found in typing.Generic.__init_subclass__
-    on the CPython main branch.
-    """
-    tvars = []
-    if '__orig_bases__' in cls.__dict__:
-        tvars = _collect_type_vars(cls.__orig_bases__)
-        # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
-        # If found, tvars must be a subset of it.
-        # If not found, tvars is it.
-        # Also check for and reject plain Generic,
-        # and reject multiple Generic[...] and/or Protocol[...].
-        gvars = None
-        for base in cls.__orig_bases__:
-            if (isinstance(base, typing._GenericAlias) and
-                    base.__origin__ in (typing.Generic, Protocol)):
-                # for error messages
-                the_base = base.__origin__.__name__
-                if gvars is not None:
-                    raise TypeError(
-                        "Cannot inherit from Generic[...]"
-                        " and/or Protocol[...] multiple types.")
-                gvars = base.__parameters__
-        if gvars is None:
-            gvars = tvars
-        else:
-            tvarset = set(tvars)
-            gvarset = set(gvars)
-            if not tvarset <= gvarset:
-                s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
-                s_args = ', '.join(str(g) for g in gvars)
-                raise TypeError(f"Some type variables ({s_vars}) are"
-                                f" not listed in {the_base}[{s_args}]")
-            tvars = gvars
-    cls.__parameters__ = tuple(tvars)
-
-
 def _caller(depth=2):
     try:
         return sys._getframe(depth).f_globals.get('__name__', '__main__')
@@ -598,17 +518,10 @@
         if type(self)._is_protocol:
             raise TypeError('Protocols cannot be instantiated')
 
-    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)
-    else:
-        _typing_Protocol = _marker
-        _ProtocolMetaBase = abc.ABCMeta
-
-    class _ProtocolMeta(_ProtocolMetaBase):
+    # 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"
+    class _ProtocolMeta(type(typing.Protocol)):
         # This metaclass is somewhat unfortunate,
         # but is necessary for several reasons...
         #
@@ -618,10 +531,10 @@
         def __new__(mcls, name, bases, namespace, **kwargs):
             if name == "Protocol" and len(bases) < 2:
                 pass
-            elif {Protocol, _typing_Protocol} & set(bases):
+            elif {Protocol, typing.Protocol} & set(bases):
                 for base in bases:
                     if not (
-                        base in {object, typing.Generic, Protocol, _typing_Protocol}
+                        base in {object, typing.Generic, Protocol, typing.Protocol}
                         or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
                         or is_protocol(base)
                     ):
@@ -699,12 +612,10 @@
         def __eq__(cls, other):
             # Hack so that typing.Generic.__class_getitem__
             # treats typing_extensions.Protocol
-            # as equivalent to typing.Protocol on Python 3.8+
+            # as equivalent to typing.Protocol
             if abc.ABCMeta.__eq__(cls, other) is True:
                 return True
-            return (
-                cls is Protocol and other is getattr(typing, "Protocol", object())
-            )
+            return cls is Protocol and other is typing.Protocol
 
         # This has to be defined, or the abc-module cache
         # complains about classes with this metaclass being unhashable,
@@ -737,146 +648,33 @@
                 return NotImplemented
         return True
 
-    if sys.version_info >= (3, 8):
-        class Protocol(typing.Generic, metaclass=_ProtocolMeta):
-            __doc__ = typing.Protocol.__doc__
-            __slots__ = ()
-            _is_protocol = True
-            _is_runtime_protocol = False
+    class Protocol(typing.Generic, metaclass=_ProtocolMeta):
+        __doc__ = typing.Protocol.__doc__
+        __slots__ = ()
+        _is_protocol = True
+        _is_runtime_protocol = False
 
-            def __init_subclass__(cls, *args, **kwargs):
-                super().__init_subclass__(*args, **kwargs)
+        def __init_subclass__(cls, *args, **kwargs):
+            super().__init_subclass__(*args, **kwargs)
 
-                # Determine if this is a protocol or a concrete subclass.
-                if not cls.__dict__.get('_is_protocol', False):
-                    cls._is_protocol = any(b is Protocol for b in cls.__bases__)
+            # Determine if this is a protocol or a concrete subclass.
+            if not cls.__dict__.get('_is_protocol', False):
+                cls._is_protocol = any(b is Protocol for b in cls.__bases__)
 
-                # Set (or override) the protocol subclass hook.
-                if '__subclasshook__' not in cls.__dict__:
-                    cls.__subclasshook__ = _proto_hook
+            # Set (or override) the protocol subclass hook.
+            if '__subclasshook__' not in cls.__dict__:
+                cls.__subclasshook__ = _proto_hook
 
-                # Prohibit instantiation for protocol classes
-                if cls._is_protocol and cls.__init__ is Protocol.__init__:
-                    cls.__init__ = _no_init
-
-    else:
-        class Protocol(metaclass=_ProtocolMeta):
-            # There is quite a lot of overlapping code with typing.Generic.
-            # Unfortunately it is hard to avoid this on Python <3.8,
-            # as the typing module on Python 3.7 doesn't let us subclass typing.Generic!
-            """Base class for protocol classes. Protocol classes are defined as::
-
-                class Proto(Protocol):
-                    def meth(self) -> int:
-                        ...
-
-            Such classes are primarily used with static type checkers that recognize
-            structural subtyping (static duck-typing), for example::
-
-                class C:
-                    def meth(self) -> int:
-                        return 0
-
-                def func(x: Proto) -> int:
-                    return x.meth()
-
-                func(C())  # Passes static type check
-
-            See PEP 544 for details. Protocol classes decorated with
-            @typing_extensions.runtime_checkable act
-            as simple-minded runtime-checkable protocols that check
-            only the presence of given attributes, ignoring their type signatures.
-
-            Protocol classes can be generic, they are defined as::
-
-                class GenProto(Protocol[T]):
-                    def meth(self) -> T:
-                        ...
-            """
-            __slots__ = ()
-            _is_protocol = True
-            _is_runtime_protocol = False
-
-            def __new__(cls, *args, **kwds):
-                if cls is Protocol:
-                    raise TypeError("Type Protocol cannot be instantiated; "
-                                    "it can only be used as a base class")
-                return super().__new__(cls)
-
-            @typing._tp_cache
-            def __class_getitem__(cls, params):
-                if not isinstance(params, tuple):
-                    params = (params,)
-                if not params and cls is not typing.Tuple:
-                    raise TypeError(
-                        f"Parameter list to {cls.__qualname__}[...] cannot be empty")
-                msg = "Parameters to generic types must be types."
-                params = tuple(typing._type_check(p, msg) for p in params)
-                if cls is Protocol:
-                    # Generic can only be subscripted with unique type variables.
-                    if not all(isinstance(p, typing.TypeVar) for p in params):
-                        i = 0
-                        while isinstance(params[i], typing.TypeVar):
-                            i += 1
-                        raise TypeError(
-                            "Parameters to Protocol[...] must all be type variables."
-                            f" Parameter {i + 1} is {params[i]}")
-                    if len(set(params)) != len(params):
-                        raise TypeError(
-                            "Parameters to Protocol[...] must all be unique")
-                else:
-                    # Subscripting a regular Generic subclass.
-                    _check_generic(cls, params, len(cls.__parameters__))
-                return typing._GenericAlias(cls, params)
-
-            def __init_subclass__(cls, *args, **kwargs):
-                if '__orig_bases__' in cls.__dict__:
-                    error = typing.Generic in cls.__orig_bases__
-                else:
-                    error = typing.Generic in cls.__bases__
-                if error:
-                    raise TypeError("Cannot inherit from plain Generic")
-                _maybe_adjust_parameters(cls)
-
-                # Determine if this is a protocol or a concrete subclass.
-                if not cls.__dict__.get('_is_protocol', None):
-                    cls._is_protocol = any(b is Protocol for b in cls.__bases__)
-
-                # Set (or override) the protocol subclass hook.
-                if '__subclasshook__' not in cls.__dict__:
-                    cls.__subclasshook__ = _proto_hook
-
-                # Prohibit instantiation for protocol classes
-                if cls._is_protocol and cls.__init__ is Protocol.__init__:
-                    cls.__init__ = _no_init
+            # Prohibit instantiation for protocol classes
+            if cls._is_protocol and cls.__init__ is Protocol.__init__:
+                cls.__init__ = _no_init
 
 
-if sys.version_info >= (3, 8):
-    runtime_checkable = typing.runtime_checkable
-else:
-    def runtime_checkable(cls):
-        """Mark a protocol class as a runtime protocol, so that it
-        can be used with isinstance() and issubclass(). Raise TypeError
-        if applied to a non-protocol class.
-
-        This allows a simple-minded structural check very similar to the
-        one-offs in collections.abc such as Hashable.
-        """
-        if not (
-            (isinstance(cls, _ProtocolMeta) or issubclass(cls, typing.Generic))
-            and getattr(cls, "_is_protocol", False)
-        ):
-            raise TypeError('@runtime_checkable can be only applied to protocol classes,'
-                            f' got {cls!r}')
-        cls._is_runtime_protocol = True
-        return cls
+# The "runtime" alias exists for backwards compatibility.
+runtime = runtime_checkable = typing.runtime_checkable
 
 
-# Exists for backwards compatibility.
-runtime = runtime_checkable
-
-
-# Our version of runtime-checkable protocols is faster on Python 3.7-3.11
+# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
 if sys.version_info >= (3, 12):
     SupportsInt = typing.SupportsInt
     SupportsFloat = typing.SupportsFloat
@@ -986,11 +784,6 @@
     # 3.10.0 and later
     _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters
 
-    if sys.version_info >= (3, 8):
-        _fake_name = "Protocol"
-    else:
-        _fake_name = "_Protocol"
-
     class _TypedDictMeta(type):
         def __new__(cls, name, bases, ns, total=True):
             """Create new typed dict class object.
@@ -1011,10 +804,10 @@
                 generic_base = ()
 
             # typing.py generally doesn't let you inherit from plain Generic, unless
-            # the name of the class happens to be "Protocol" (or "_Protocol" on 3.7).
-            tp_dict = type.__new__(_TypedDictMeta, _fake_name, (*generic_base, dict), ns)
+            # the name of the class happens to be "Protocol"
+            tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
             tp_dict.__name__ = name
-            if tp_dict.__qualname__ == _fake_name:
+            if tp_dict.__qualname__ == "Protocol":
                 tp_dict.__qualname__ = name
 
             if not hasattr(tp_dict, '__orig_bases__'):
@@ -1077,7 +870,7 @@
     _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
 
     @_ensure_subclassable(lambda bases: (_TypedDict,))
-    def TypedDict(__typename, __fields=_marker, *, total=True, **kwargs):
+    def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs):
         """A simple typed namespace. At runtime it is equivalent to a plain dict.
 
         TypedDict creates a dictionary type such that a type checker will expect all
@@ -1124,20 +917,20 @@
 
         See PEP 655 for more details on Required and NotRequired.
         """
-        if __fields is _marker or __fields is None:
-            if __fields is _marker:
+        if fields is _marker or fields is None:
+            if fields is _marker:
                 deprecated_thing = "Failing to pass a value for the 'fields' parameter"
             else:
                 deprecated_thing = "Passing `None` as the 'fields' parameter"
 
-            example = f"`{__typename} = TypedDict({__typename!r}, {{}})`"
+            example = f"`{typename} = TypedDict({typename!r}, {{}})`"
             deprecation_msg = (
                 f"{deprecated_thing} is deprecated and will be disallowed in "
                 "Python 3.15. To create a TypedDict class with 0 fields "
                 "using the functional syntax, pass an empty dictionary, e.g. "
             ) + example + "."
             warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
-            __fields = kwargs
+            fields = kwargs
         elif kwargs:
             raise TypeError("TypedDict takes either a dict or keyword arguments,"
                             " but not both")
@@ -1150,13 +943,13 @@
                 stacklevel=2,
             )
 
-        ns = {'__annotations__': dict(__fields)}
+        ns = {'__annotations__': dict(fields)}
         module = _caller()
         if module is not None:
             # Setting correct module is necessary to make typed dict classes pickleable.
             ns['__module__'] = module
 
-        td = _TypedDictMeta(__typename, (), ns, total=total)
+        td = _TypedDictMeta(typename, (), ns, total=total)
         td.__orig_bases__ = (TypedDict,)
         return td
 
@@ -1186,7 +979,7 @@
     assert_type = typing.assert_type
 
 else:
-    def assert_type(__val, __typ):
+    def assert_type(val, typ, /):
         """Assert (to the type checker) that the value is of the given type.
 
         When the type checker encounters a call to assert_type(), it
@@ -1199,12 +992,12 @@
         At runtime this returns the first argument unchanged and otherwise
         does nothing.
         """
-        return __val
+        return val
 
 
-if hasattr(typing, "Required"):
+if hasattr(typing, "Required"):  # 3.11+
     get_type_hints = typing.get_type_hints
-else:
+else:  # <=3.10
     # replaces _strip_annotations()
     def _strip_extras(t):
         """Strips Annotated, Required and NotRequired from a given type."""
@@ -1262,11 +1055,11 @@
         - If two dict arguments are passed, they specify globals and
           locals, respectively.
         """
-        if hasattr(typing, "Annotated"):
+        if hasattr(typing, "Annotated"):  # 3.9+
             hint = typing.get_type_hints(
                 obj, globalns=globalns, localns=localns, include_extras=True
             )
-        else:
+        else:  # 3.8
             hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
         if include_extras:
             return hint
@@ -1279,7 +1072,7 @@
     # Not exported and not a public API, but needed for get_origin() and get_args()
     # to work.
     _AnnotatedAlias = typing._AnnotatedAlias
-# 3.7-3.8
+# 3.8
 else:
     class _AnnotatedAlias(typing._GenericAlias, _root=True):
         """Runtime representation of an annotated type.
@@ -1384,7 +1177,7 @@
 if sys.version_info[:2] >= (3, 10):
     get_origin = typing.get_origin
     get_args = typing.get_args
-# 3.7-3.9
+# 3.8-3.9
 else:
     try:
         # 3.9+
@@ -1462,7 +1255,7 @@
         It's invalid when used anywhere except as in the example above.
         """
         raise TypeError(f"{self} is not subscriptable")
-# 3.7-3.8
+# 3.8
 else:
     TypeAlias = _ExtensionsSpecialForm(
         'TypeAlias',
@@ -1519,7 +1312,7 @@
                 covariant=False, contravariant=False,
                 default=_marker, infer_variance=False):
         if hasattr(typing, "TypeAliasType"):
-            # PEP 695 implemented, can pass infer_variance to typing.TypeVar
+            # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
             typevar = typing.TypeVar(name, *constraints, bound=bound,
                                      covariant=covariant, contravariant=contravariant,
                                      infer_variance=infer_variance)
@@ -1541,7 +1334,7 @@
 if hasattr(typing, 'ParamSpecArgs'):
     ParamSpecArgs = typing.ParamSpecArgs
     ParamSpecKwargs = typing.ParamSpecKwargs
-# 3.7-3.9
+# 3.8-3.9
 else:
     class _Immutable:
         """Mixin to indicate that object should not be copied."""
@@ -1630,7 +1423,7 @@
         def __init_subclass__(cls) -> None:
             raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")
 
-# 3.7-3.9
+# 3.8-3.9
 else:
 
     # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
@@ -1735,7 +1528,7 @@
             pass
 
 
-# 3.7-3.9
+# 3.8-3.9
 if not hasattr(typing, 'Concatenate'):
     # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
     class _ConcatenateGenericAlias(list):
@@ -1770,7 +1563,7 @@
             )
 
 
-# 3.7-3.9
+# 3.8-3.9
 @typing._tp_cache
 def _concatenate_getitem(self, parameters):
     if parameters == ():
@@ -1804,7 +1597,7 @@
         See PEP 612 for detailed information.
         """
         return _concatenate_getitem(self, parameters)
-# 3.7-8
+# 3.8
 else:
     class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
         def __getitem__(self, parameters):
@@ -1874,7 +1667,7 @@
         """
         item = typing._type_check(parameters, f'{self} accepts only a single type.')
         return typing._GenericAlias(self, (item,))
-# 3.7-3.8
+# 3.8
 else:
     class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
         def __getitem__(self, parameters):
@@ -1972,7 +1765,7 @@
         return self._getitem(self, parameters)
 
 
-if hasattr(typing, "LiteralString"):
+if hasattr(typing, "LiteralString"):  # 3.11+
     LiteralString = typing.LiteralString
 else:
     @_SpecialForm
@@ -1995,7 +1788,7 @@
         raise TypeError(f"{self} is not subscriptable")
 
 
-if hasattr(typing, "Self"):
+if hasattr(typing, "Self"):  # 3.11+
     Self = typing.Self
 else:
     @_SpecialForm
@@ -2016,7 +1809,7 @@
         raise TypeError(f"{self} is not subscriptable")
 
 
-if hasattr(typing, "Never"):
+if hasattr(typing, "Never"):  # 3.11+
     Never = typing.Never
 else:
     @_SpecialForm
@@ -2046,10 +1839,10 @@
         raise TypeError(f"{self} is not subscriptable")
 
 
-if hasattr(typing, 'Required'):
+if hasattr(typing, 'Required'):  # 3.11+
     Required = typing.Required
     NotRequired = typing.NotRequired
-elif sys.version_info[:2] >= (3, 9):
+elif sys.version_info[:2] >= (3, 9):  # 3.9-3.10
     @_ExtensionsSpecialForm
     def Required(self, parameters):
         """A special typing construct to mark a key of a total=False TypedDict
@@ -2087,7 +1880,7 @@
         item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
         return typing._GenericAlias(self, (item,))
 
-else:
+else:  # 3.8
     class _RequiredForm(_ExtensionsSpecialForm, _root=True):
         def __getitem__(self, parameters):
             item = typing._type_check(parameters,
@@ -2175,7 +1968,7 @@
     def _is_unpack(obj):
         return get_origin(obj) is Unpack
 
-elif sys.version_info[:2] >= (3, 9):
+elif sys.version_info[:2] >= (3, 9):  # 3.9+
     class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
         def __init__(self, getitem):
             super().__init__(getitem)
@@ -2192,7 +1985,7 @@
     def _is_unpack(obj):
         return isinstance(obj, _UnpackAlias)
 
-else:
+else:  # 3.8
     class _UnpackAlias(typing._GenericAlias, _root=True):
         __class__ = typing.TypeVar
 
@@ -2225,7 +2018,7 @@
         def __init_subclass__(self, *args, **kwds):
             raise TypeError("Cannot subclass special typing classes")
 
-else:
+else:  # <=3.10
     class TypeVarTuple(_DefaultMixin):
         """Type variable tuple.
 
@@ -2304,10 +2097,10 @@
                 raise TypeError("Cannot subclass special typing classes")
 
 
-if hasattr(typing, "reveal_type"):
+if hasattr(typing, "reveal_type"):  # 3.11+
     reveal_type = typing.reveal_type
-else:
-    def reveal_type(__obj: T) -> T:
+else:  # <=3.10
+    def reveal_type(obj: T, /) -> T:
         """Reveal the inferred type of a variable.
 
         When a static type checker encounters a call to ``reveal_type()``,
@@ -2323,14 +2116,14 @@
         argument and returns it unchanged.
 
         """
-        print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr)
-        return __obj
+        print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
+        return obj
 
 
-if hasattr(typing, "assert_never"):
+if hasattr(typing, "assert_never"):  # 3.11+
     assert_never = typing.assert_never
-else:
-    def assert_never(__arg: Never) -> Never:
+else:  # <=3.10
+    def assert_never(arg: Never, /) -> Never:
         """Assert to the type checker that a line of code is unreachable.
 
         Example::
@@ -2353,10 +2146,10 @@
         raise AssertionError("Expected code to be unreachable")
 
 
-if sys.version_info >= (3, 12):
+if sys.version_info >= (3, 12):  # 3.12+
     # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
     dataclass_transform = typing.dataclass_transform
-else:
+else:  # <=3.11
     def dataclass_transform(
         *,
         eq_default: bool = True,
@@ -2443,12 +2236,12 @@
         return decorator
 
 
-if hasattr(typing, "override"):
+if hasattr(typing, "override"):  # 3.12+
     override = typing.override
-else:
+else:  # <=3.11
     _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
 
-    def override(__arg: _F) -> _F:
+    def override(arg: _F, /) -> _F:
         """Indicate that a method is intended to override a method in a base class.
 
         Usage:
@@ -2475,13 +2268,13 @@
 
         """
         try:
-            __arg.__override__ = True
+            arg.__override__ = True
         except (AttributeError, TypeError):
             # Skip the attribute silently if it is not writable.
             # AttributeError happens if the object has __slots__ or a
             # read-only property, TypeError if it's a builtin class.
             pass
-        return __arg
+        return arg
 
 
 if hasattr(typing, "deprecated"):
@@ -2490,7 +2283,8 @@
     _T = typing.TypeVar("_T")
 
     def deprecated(
-        __msg: str,
+        msg: str,
+        /,
         *,
         category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
         stacklevel: int = 1,
@@ -2533,17 +2327,17 @@
         See PEP 702 for details.
 
         """
-        def decorator(__arg: _T) -> _T:
+        def decorator(arg: _T, /) -> _T:
             if category is None:
-                __arg.__deprecated__ = __msg
-                return __arg
-            elif isinstance(__arg, type):
-                original_new = __arg.__new__
-                has_init = __arg.__init__ is not object.__init__
+                arg.__deprecated__ = msg
+                return arg
+            elif isinstance(arg, type):
+                original_new = arg.__new__
+                has_init = arg.__init__ is not object.__init__
 
                 @functools.wraps(original_new)
                 def __new__(cls, *args, **kwargs):
-                    warnings.warn(__msg, category=category, stacklevel=stacklevel + 1)
+                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
                     if original_new is not object.__new__:
                         return original_new(cls, *args, **kwargs)
                     # Mirrors a similar check in object.__new__.
@@ -2552,21 +2346,21 @@
                     else:
                         return original_new(cls)
 
-                __arg.__new__ = staticmethod(__new__)
-                __arg.__deprecated__ = __new__.__deprecated__ = __msg
-                return __arg
-            elif callable(__arg):
-                @functools.wraps(__arg)
+                arg.__new__ = staticmethod(__new__)
+                arg.__deprecated__ = __new__.__deprecated__ = msg
+                return arg
+            elif callable(arg):
+                @functools.wraps(arg)
                 def wrapper(*args, **kwargs):
-                    warnings.warn(__msg, category=category, stacklevel=stacklevel + 1)
-                    return __arg(*args, **kwargs)
+                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+                    return arg(*args, **kwargs)
 
-                __arg.__deprecated__ = wrapper.__deprecated__ = __msg
+                arg.__deprecated__ = wrapper.__deprecated__ = msg
                 return wrapper
             else:
                 raise TypeError(
                     "@deprecated decorator with non-None category must be applied to "
-                    f"a class or callable, not {__arg!r}"
+                    f"a class or callable, not {arg!r}"
                 )
 
         return decorator
@@ -2584,7 +2378,7 @@
     typing._check_generic = _check_generic
 
 
-# Backport typing.NamedTuple as it exists in Python 3.12.
+# Backport typing.NamedTuple as it exists in Python 3.13.
 # In 3.11, the ability to define generic `NamedTuple`s was supported.
 # This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
 # On 3.12, we added __orig_bases__ to call-based NamedTuples
@@ -2655,7 +2449,7 @@
         return (_NamedTuple,)
 
     @_ensure_subclassable(_namedtuple_mro_entries)
-    def NamedTuple(__typename, __fields=_marker, **kwargs):
+    def NamedTuple(typename, fields=_marker, /, **kwargs):
         """Typed version of namedtuple.
 
         Usage::
@@ -2675,7 +2469,7 @@
 
             Employee = NamedTuple('Employee', [('name', str), ('id', int)])
         """
-        if __fields is _marker:
+        if fields is _marker:
             if kwargs:
                 deprecated_thing = "Creating NamedTuple classes using keyword arguments"
                 deprecation_msg = (
@@ -2684,14 +2478,14 @@
                 )
             else:
                 deprecated_thing = "Failing to pass a value for the 'fields' parameter"
-                example = f"`{__typename} = NamedTuple({__typename!r}, [])`"
+                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                 deprecation_msg = (
                     "{name} is deprecated and will be disallowed in Python {remove}. "
                     "To create a NamedTuple class with 0 fields "
                     "using the functional syntax, "
                     "pass an empty list, e.g. "
                 ) + example + "."
-        elif __fields is None:
+        elif fields is None:
             if kwargs:
                 raise TypeError(
                     "Cannot pass `None` as the 'fields' parameter "
@@ -2699,7 +2493,7 @@
                 )
             else:
                 deprecated_thing = "Passing `None` as the 'fields' parameter"
-                example = f"`{__typename} = NamedTuple({__typename!r}, [])`"
+                example = f"`{typename} = NamedTuple({typename!r}, [])`"
                 deprecation_msg = (
                     "{name} is deprecated and will be disallowed in Python {remove}. "
                     "To create a NamedTuple class with 0 fields "
@@ -2709,27 +2503,17 @@
         elif kwargs:
             raise TypeError("Either list of fields or keywords"
                             " can be provided to NamedTuple, not both")
-        if __fields is _marker or __fields is None:
+        if fields is _marker or fields is None:
             warnings.warn(
                 deprecation_msg.format(name=deprecated_thing, remove="3.15"),
                 DeprecationWarning,
                 stacklevel=2,
             )
-            __fields = kwargs.items()
-        nt = _make_nmtuple(__typename, __fields, module=_caller())
+            fields = kwargs.items()
+        nt = _make_nmtuple(typename, fields, module=_caller())
         nt.__orig_bases__ = (NamedTuple,)
         return nt
 
-    # On 3.8+, alter the signature so that it matches typing.NamedTuple.
-    # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
-    # so just leave the signature as it is on 3.7.
-    if sys.version_info >= (3, 8):
-        _new_signature = '(typename, fields=None, /, **kwargs)'
-        if isinstance(NamedTuple, _types.FunctionType):
-            NamedTuple.__text_signature__ = _new_signature
-        else:
-            NamedTuple.__call__.__text_signature__ = _new_signature
-
 
 if hasattr(collections.abc, "Buffer"):
     Buffer = collections.abc.Buffer
@@ -2764,7 +2548,7 @@
 if hasattr(_types, "get_original_bases"):
     get_original_bases = _types.get_original_bases
 else:
-    def get_original_bases(__cls):
+    def get_original_bases(cls, /):
         """Return the class's "original" bases prior to modification by `__mro_entries__`.
 
         Examples::
@@ -2786,13 +2570,13 @@
             assert get_original_bases(int) == (object,)
         """
         try:
-            return __cls.__orig_bases__
+            return cls.__orig_bases__
         except AttributeError:
             try:
-                return __cls.__bases__
+                return cls.__bases__
             except AttributeError:
                 raise TypeError(
-                    f'Expected an instance of type, not {type(__cls).__name__!r}'
+                    f'Expected an instance of type, not {type(cls).__name__!r}'
                 ) from None
 
 
@@ -2920,13 +2704,13 @@
             # Setting this attribute closes the TypeAliasType from further modification
             self.__name__ = name
 
-        def __setattr__(self, __name: str, __value: object) -> None:
+        def __setattr__(self, name: str, value: object, /) -> None:
             if hasattr(self, "__name__"):
-                self._raise_attribute_error(__name)
-            super().__setattr__(__name, __value)
+                self._raise_attribute_error(name)
+            super().__setattr__(name, value)
 
-        def __delattr__(self, __name: str) -> Never:
-            self._raise_attribute_error(__name)
+        def __delattr__(self, name: str, /) -> Never:
+            self._raise_attribute_error(name)
 
         def _raise_attribute_error(self, name: str) -> Never:
             # Match the Python 3.12 error messages exactly
@@ -2987,7 +2771,7 @@
     is_protocol = typing.is_protocol
     get_protocol_members = typing.get_protocol_members
 else:
-    def is_protocol(__tp: type) -> bool:
+    def is_protocol(tp: type, /) -> bool:
         """Return True if the given type is a Protocol.
 
         Example::
@@ -3002,13 +2786,13 @@
             False
         """
         return (
-            isinstance(__tp, type)
-            and getattr(__tp, '_is_protocol', False)
-            and __tp is not Protocol
-            and __tp is not getattr(typing, "Protocol", object())
+            isinstance(tp, type)
+            and getattr(tp, '_is_protocol', False)
+            and tp is not Protocol
+            and tp is not typing.Protocol
         )
 
-    def get_protocol_members(__tp: type) -> typing.FrozenSet[str]:
+    def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
         """Return the set of members defined in a Protocol.
 
         Example::
@@ -3022,11 +2806,11 @@
 
         Raise a TypeError for arguments that are not Protocols.
         """
-        if not is_protocol(__tp):
-            raise TypeError(f'{__tp!r} is not a Protocol')
-        if hasattr(__tp, '__protocol_attrs__'):
-            return frozenset(__tp.__protocol_attrs__)
-        return frozenset(_get_protocol_attrs(__tp))
+        if not is_protocol(tp):
+            raise TypeError(f'{tp!r} is not a Protocol')
+        if hasattr(tp, '__protocol_attrs__'):
+            return frozenset(tp.__protocol_attrs__)
+        return frozenset(_get_protocol_attrs(tp))
 
 
 # Aliases for items that have always been in typing.
diff --git a/tox.ini b/tox.ini
index 3d583ef..5bed022 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 isolated_build = True
-envlist = py37, py38, py39, py310, py311, py312
+envlist = py38, py39, py310, py311, py312
 
 [testenv]
 changedir = src