From dc4fb2a2235bba0d3af95561491d1f728c0b0bd8 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Tue, 27 Jun 2023 18:34:51 +1000
Subject: [PATCH 01/56] Add a protocol for trio._core._run._TaskStatus

---
 trio/__init__.py   |  1 +
 trio/_core/_run.py | 38 ++++++++++++++++++++++++++------------
 2 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/trio/__init__.py b/trio/__init__.py
index be7de42cde..997af9ff15 100644
--- a/trio/__init__.py
+++ b/trio/__init__.py
@@ -31,6 +31,7 @@
     BusyResourceError as BusyResourceError,
     Cancelled as Cancelled,
     CancelScope as CancelScope,
+    TaskStatus as TaskStatus,
     ClosedResourceError as ClosedResourceError,
     EndOfChannel as EndOfChannel,
     Nursery as Nursery,
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index ce8feb2827..06b5862fb3 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -17,7 +17,7 @@
 from math import inf
 from time import perf_counter
 from types import TracebackType
-from typing import TYPE_CHECKING, Any, NoReturn, TypeVar
+from typing import TYPE_CHECKING, Any, NoReturn, overload, Protocol, TypeVar
 
 import attr
 from outcome import Error, Outcome, Value, capture
@@ -58,6 +58,7 @@
 _NO_SEND: FinalT = object()
 
 FnT = TypeVar("FnT", bound="Callable[..., Any]")
+StatusT = TypeVar("StatusT", contravariant=True)
 
 
 # Decorator to mark methods public. This does nothing by itself, but
@@ -780,19 +781,32 @@ def cancel_called(self) -> bool:
 ################################################################
 
 
+class TaskStatus(Protocol[StatusT]):
+    """Defines the behaviour for the parameter passed to Nursery.start() functions."""
+    @overload
+    def started(self: TaskStatus[None]) -> None: ...
+    @overload
+    def started(self, value: StatusT) -> None: ...
+
+
 # This code needs to be read alongside the code from Nursery.start to make
 # sense.
 @attr.s(eq=False, hash=False, repr=False)
-class TaskStatus(metaclass=Final):
-    _old_nursery = attr.ib()
-    _new_nursery = attr.ib()
-    _called_started = attr.ib(default=False)
-    _value = attr.ib(default=None)
+class _TaskStatus(metaclass=Final, TaskStatus[StatusT]):
+    _old_nursery: Nursery = attr.ib()
+    _new_nursery: Nursery = attr.ib()
+    _called_started: bool = attr.ib(default=False)
+    _value: StatusT | None = attr.ib(default=None)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return f"<Task status object at {id(self):#x}>"
 
-    def started(self, value=None):
+    @overload
+    def started(self: TaskStatus[None]) -> None: ...
+    @overload
+    def started(self, value: StatusT) -> None: ...
+
+    def started(self, value: StatusT | None = None) -> None:
         if self._called_started:
             raise RuntimeError("called 'started' twice on the same task status")
         self._called_started = True
@@ -1137,7 +1151,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
         try:
             self._pending_starts += 1
             async with open_nursery() as old_nursery:
-                task_status = TaskStatus(old_nursery, self)
+                task_status = _TaskStatus(old_nursery, self)
                 thunk = functools.partial(async_fn, task_status=task_status)
                 task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
                     thunk, args, old_nursery, name
@@ -2461,15 +2475,15 @@ def unrolled_run(
 ################################################################
 
 
-class _TaskStatusIgnored:
+class _TaskStatusIgnored(TaskStatus[Any]):
     def __repr__(self) -> str:
         return "TASK_STATUS_IGNORED"
 
-    def started(self, value: object = None) -> None:
+    def started(self, value: Any = None) -> None:
         pass
 
 
-TASK_STATUS_IGNORED: FinalT = _TaskStatusIgnored()
+TASK_STATUS_IGNORED: FinalT[TaskStatus[Any]] = _TaskStatusIgnored()
 
 
 def current_task() -> Task:

From fcf981ac9b076790a8e99ae9337479d95af4a039 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 14 Jul 2023 16:59:16 +1000
Subject: [PATCH 02/56] Document TaskStatus a little, link it into the start()
 documentation

---
 docs/source/conf.py            |  1 +
 docs/source/reference-core.rst |  4 ++++
 trio/_core/_run.py             | 30 +++++++++++++++++++-----------
 3 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0e16b2d426..224ad507bf 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -62,6 +62,7 @@
     ("py:obj", "trio._abc.SendType"),
     ("py:obj", "trio._abc.T"),
     ("py:obj", "trio._abc.T_resource"),
+    ("py:class", "trio._core._run.StatusT"),
     ("py:class", "trio._threads.T"),
     # why aren't these found in stdlib?
     ("py:class", "types.FrameType"),
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 980a3106e5..d3fd970b5f 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -918,7 +918,11 @@ The nursery API
 .. autoclass:: Nursery()
    :members:
 
+.. autoclass:: TaskStatus(Protocol[StatusT])
+   :members:
+
 .. attribute:: TASK_STATUS_IGNORED
+   :type: TaskStatus
 
    See :meth:`~Nursery.start`.
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 06b5862fb3..1ed3529dc8 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -782,12 +782,21 @@ def cancel_called(self) -> bool:
 
 
 class TaskStatus(Protocol[StatusT]):
-    """Defines the behaviour for the parameter passed to Nursery.start() functions."""
+    """The interface provided by :meth:`Nursery.start()` to the spawned task.
+
+    This is provided via the ``task_status`` keyword-only parameter.
+    """
     @overload
     def started(self: TaskStatus[None]) -> None: ...
     @overload
     def started(self, value: StatusT) -> None: ...
 
+    def started(self, value: StatusT | None = None) -> None:
+        """Tasks call this method to indicate that they have initialized.
+
+        See `nursery.start() <trio.Nursery.start>` for more information.
+        """
+
 
 # This code needs to be read alongside the code from Nursery.start to make
 # sense.
@@ -1114,7 +1123,7 @@ async def start(self, async_fn, *args, name=None):
 
         The ``async_fn`` must accept a ``task_status`` keyword argument,
         and it must make sure that it (or someone) eventually calls
-        ``task_status.started()``.
+        :meth:`task_status.started() <TaskStatus.started>`.
 
         The conventional way to define ``async_fn`` is like::
 
@@ -1127,24 +1136,23 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
         a do-nothing ``started`` method. This way your function supports
         being called either like ``await nursery.start(async_fn, arg1,
         arg2)`` or directly like ``await async_fn(arg1, arg2)``, and
-        either way it can call ``task_status.started()`` without
-        worrying about which mode it's in. Defining your function like
+        either way it can call :meth:`task_status.started() <TaskStatus.started>`
+        without worrying about which mode it's in. Defining your function like
         this will make it obvious to readers that it supports being used
         in both modes.
 
-        Before the child calls ``task_status.started()``, it's
-        effectively run underneath the call to :meth:`start`: if it
+        Before the child calls :meth:`task_status.started() <TaskStatus.started>`,
+        it's effectively run underneath the call to :meth:`start`: if it
         raises an exception then that exception is reported by
         :meth:`start`, and does *not* propagate out of the nursery. If
         :meth:`start` is cancelled, then the child task is also
         cancelled.
 
-        When the child calls ``task_status.started()``, it's moved out
-        from underneath :meth:`start` and into the given nursery.
+        When the child calls :meth:`task_status.started() <TaskStatus.started>`,
+        it's moved out from underneath :meth:`start` and into the given nursery.
 
-        If the child task passes a value to
-        ``task_status.started(value)``, then :meth:`start` returns this
-        value. Otherwise it returns ``None``.
+        If the child task passes a value to :meth:`task_status.started(value) <TaskStatus.started>`,
+        then :meth:`start` returns this value. Otherwise, it returns ``None``.
         """
         if self._closed:
             raise RuntimeError("Nursery is closed to new arrivals")

From 99f30240af176f122ff7850bcfc9aff092334a0a Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 29 Jul 2023 10:52:07 +1000
Subject: [PATCH 03/56] Fix variance, typecheck return type of Nursery.start()

---
 trio/__init__.py   |  1 -
 trio/_core/_run.py | 49 ++++++++++++++++++++++++++++++++++------------
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/trio/__init__.py b/trio/__init__.py
index 997af9ff15..be7de42cde 100644
--- a/trio/__init__.py
+++ b/trio/__init__.py
@@ -31,7 +31,6 @@
     BusyResourceError as BusyResourceError,
     Cancelled as Cancelled,
     CancelScope as CancelScope,
-    TaskStatus as TaskStatus,
     ClosedResourceError as ClosedResourceError,
     EndOfChannel as EndOfChannel,
     Nursery as Nursery,
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 1ed3529dc8..a3305ccd40 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -10,14 +10,14 @@
 import threading
 import warnings
 from collections import deque
-from collections.abc import Callable, Coroutine, Iterator
+from collections.abc import Awaitable, Callable, Coroutine, Iterator
 from contextlib import AbstractAsyncContextManager, contextmanager
 from contextvars import copy_context
 from heapq import heapify, heappop, heappush
 from math import inf
 from time import perf_counter
 from types import TracebackType
-from typing import TYPE_CHECKING, Any, NoReturn, overload, Protocol, TypeVar
+from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, overload
 
 import attr
 from outcome import Error, Outcome, Value, capture
@@ -58,7 +58,9 @@
 _NO_SEND: FinalT = object()
 
 FnT = TypeVar("FnT", bound="Callable[..., Any]")
-StatusT = TypeVar("StatusT", contravariant=True)
+StatusT = TypeVar("StatusT")
+StatusT_co = TypeVar("StatusT_co", covariant=True)
+StatusT_contra = TypeVar("StatusT_contra", contravariant=True)
 
 
 # Decorator to mark methods public. This does nothing by itself, but
@@ -781,17 +783,21 @@ def cancel_called(self) -> bool:
 ################################################################
 
 
-class TaskStatus(Protocol[StatusT]):
+class TaskStatus(Protocol[StatusT_contra]):
     """The interface provided by :meth:`Nursery.start()` to the spawned task.
 
     This is provided via the ``task_status`` keyword-only parameter.
     """
+
     @overload
-    def started(self: TaskStatus[None]) -> None: ...
+    def started(self: TaskStatus[None]) -> None:
+        ...
+
     @overload
-    def started(self, value: StatusT) -> None: ...
+    def started(self, value: StatusT_contra) -> None:
+        ...
 
-    def started(self, value: StatusT | None = None) -> None:
+    def started(self, value: StatusT_contra | None = None) -> None:
         """Tasks call this method to indicate that they have initialized.
 
         See `nursery.start() <trio.Nursery.start>` for more information.
@@ -801,7 +807,7 @@ def started(self, value: StatusT | None = None) -> None:
 # This code needs to be read alongside the code from Nursery.start to make
 # sense.
 @attr.s(eq=False, hash=False, repr=False)
-class _TaskStatus(metaclass=Final, TaskStatus[StatusT]):
+class _TaskStatus(TaskStatus[StatusT]):
     _old_nursery: Nursery = attr.ib()
     _new_nursery: Nursery = attr.ib()
     _called_started: bool = attr.ib(default=False)
@@ -811,9 +817,12 @@ def __repr__(self) -> str:
         return f"<Task status object at {id(self):#x}>"
 
     @overload
-    def started(self: TaskStatus[None]) -> None: ...
+    def started(self: TaskStatus[None]) -> None:
+        ...
+
     @overload
-    def started(self, value: StatusT) -> None: ...
+    def started(self, value: StatusT) -> None:
+        ...
 
     def started(self, value: StatusT | None = None) -> None:
         if self._called_started:
@@ -868,6 +877,13 @@ def started(self, value: StatusT | None = None) -> None:
         self._old_nursery._check_nursery_closed()
 
 
+class _NurseryStartFunc(Protocol[StatusT_co]):
+    """Type of functions passed to Nursery.start()."""
+
+    def __call__(self, *args: Any, task_status: TaskStatus[StatusT_co]) -> object:
+        ...
+
+
 @attr.s
 class NurseryManager:
     """Nursery context manager.
@@ -1072,7 +1088,12 @@ def aborted(raise_cancel):
                 # (see test_nursery_cancel_doesnt_create_cyclic_garbage)
                 del self._pending_excs
 
-    def start_soon(self, async_fn, *args, name=None):
+    def start_soon(
+        self,
+        async_fn: Callable[..., Awaitable[object]],
+        *args: Any,
+        name: object = None,
+    ) -> None:
         """Creates a child task, scheduling ``await async_fn(*args)``.
 
         If you want to run a function and immediately wait for its result,
@@ -1114,7 +1135,9 @@ def start_soon(self, async_fn, *args, name=None):
         """
         GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)
 
-    async def start(self, async_fn, *args, name=None):
+    async def start(
+        self, async_fn: _NurseryStartFunc[StatusT], *args: Any, name: object = None
+    ) -> StatusT:
         r"""Creates and initializes a child task.
 
         Like :meth:`start_soon`, but blocks until the new task has
@@ -1159,7 +1182,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
         try:
             self._pending_starts += 1
             async with open_nursery() as old_nursery:
-                task_status = _TaskStatus(old_nursery, self)
+                task_status: _TaskStatus[StatusT] = _TaskStatus(old_nursery, self)
                 thunk = functools.partial(async_fn, task_status=task_status)
                 task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
                     thunk, args, old_nursery, name

From f51b3a77e059d296db77ccba1efa2b5aea3c91fd Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 29 Jul 2023 11:03:34 +1000
Subject: [PATCH 04/56] Allow protocols to be non-final, like ABCs

---
 trio/_tests/test_exports.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py
index 1b1e8df8da..682d5f9c49 100644
--- a/trio/_tests/test_exports.py
+++ b/trio/_tests/test_exports.py
@@ -9,6 +9,7 @@
 import sys
 from pathlib import Path
 from types import ModuleType
+from typing import Protocol
 
 import attrs
 import pytest
@@ -23,6 +24,12 @@
 mypy_cache_updated = False
 
 
+try:  # If installed, check both versions of this class.
+    from typing_extensions import Protocol as Protocol_ext
+except ImportError:
+    Protocol_ext = Protocol
+
+
 def _ensure_mypy_cache_updated():
     # This pollutes the `empty` dir. Should this be changed?
     from mypy.api import run
@@ -484,6 +491,9 @@ def test_classes_are_final():
             # point of ABCs
             if inspect.isabstract(class_):
                 continue
+            # Same with protocols, but only direct children.
+            if Protocol in class_.__bases__ or Protocol_ext in class_.__bases__:
+                continue
             # Exceptions are allowed to be subclassed, because exception
             # subclassing isn't used to inherit behavior.
             if issubclass(class_, BaseException):

From 8eff4a15ea8a4a511a48b17723beb4868af98602 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 29 Jul 2023 13:38:38 +1000
Subject: [PATCH 05/56] Add types to the rest of _run

---
 trio/_core/_run.py | 129 ++++++++++++++++++++++++++++-----------------
 1 file changed, 82 insertions(+), 47 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index a3305ccd40..95e780bab9 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -17,13 +17,15 @@
 from math import inf
 from time import perf_counter
 from types import TracebackType
-from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, overload
+from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, cast, overload
 
 import attr
 from outcome import Error, Outcome, Value, capture
 from sniffio import current_async_library_cvar
 from sortedcontainers import SortedDict
 
+import trio._core._ki
+
 from .. import _core
 from .._util import Final, NoPublicConstructor, coroutine_or_error
 from ._asyncgens import AsyncGenerators
@@ -53,9 +55,12 @@
     # An unfortunate name collision here with trio._util.Final
     from typing import Final as FinalT
 
+    from typing_extensions import Self
+
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 
-_NO_SEND: FinalT = object()
+# Passed as a sentinel
+_NO_SEND: Outcome = cast(Outcome, object())
 
 FnT = TypeVar("FnT", bound="Callable[..., Any]")
 StatusT = TypeVar("StatusT")
@@ -152,7 +157,9 @@ class IdlePrimedTypes(enum.Enum):
 ################################################################
 
 
-def collapse_exception_group(excgroup):
+def collapse_exception_group(
+    excgroup: BaseExceptionGroup[BaseException],
+) -> BaseException:
     """Recursively collapse any single-exception groups into that single contained
     exception.
 
@@ -172,7 +179,8 @@ def collapse_exception_group(excgroup):
         )
         return exceptions[0]
     elif modified:
-        return excgroup.derive(exceptions)
+        # derive() returns Any for some reason.
+        return excgroup.derive(exceptions)  # type: ignore[no-any-return]
     else:
         return excgroup
 
@@ -187,18 +195,18 @@ class Deadlines:
     """
 
     # Heap of (deadline, id(CancelScope), CancelScope)
-    _heap = attr.ib(factory=list)
+    _heap: list[tuple[float, int, CancelScope]] = attr.ib(factory=list)
     # Count of active deadlines (those that haven't been changed)
-    _active = attr.ib(default=0)
+    _active: int = attr.ib(default=0)
 
-    def add(self, deadline, cancel_scope):
+    def add(self, deadline: float, cancel_scope: CancelScope) -> None:
         heappush(self._heap, (deadline, id(cancel_scope), cancel_scope))
         self._active += 1
 
-    def remove(self, deadline, cancel_scope):
+    def remove(self, deadline: float, cancel_scope: CancelScope) -> None:
         self._active -= 1
 
-    def next_deadline(self):
+    def next_deadline(self) -> float:
         while self._heap:
             deadline, _, cancel_scope = self._heap[0]
             if deadline == cancel_scope._registered_deadline:
@@ -208,7 +216,7 @@ def next_deadline(self):
                 heappop(self._heap)
         return inf
 
-    def _prune(self):
+    def _prune(self) -> None:
         # In principle, it's possible for a cancel scope to toggle back and
         # forth repeatedly between the same two deadlines, and end up with
         # lots of stale entries that *look* like they're still active, because
@@ -229,7 +237,7 @@ def _prune(self):
         heapify(pruned_heap)
         self._heap = pruned_heap
 
-    def expire(self, now):
+    def expire(self, now: float) -> bool:
         did_something = False
         while self._heap and self._heap[0][0] <= now:
             deadline, _, cancel_scope = heappop(self._heap)
@@ -380,14 +388,14 @@ def close(self) -> None:
                 child.recalculate()
 
     @property
-    def parent_cancellation_is_visible_to_us(self):
+    def parent_cancellation_is_visible_to_us(self) -> bool:
         return (
             self._parent is not None
             and not self._scope.shield
             and self._parent.effectively_cancelled
         )
 
-    def recalculate(self):
+    def recalculate(self) -> None:
         # This does a depth-first traversal over this and descendent cancel
         # statuses, to ensure their state is up-to-date. It's basically a
         # recursive algorithm, but we use an explicit stack to avoid any
@@ -406,7 +414,7 @@ def recalculate(self):
                         task._attempt_delivery_of_any_pending_cancel()
                 todo.extend(current._children)
 
-    def _mark_abandoned(self):
+    def _mark_abandoned(self) -> None:
         self.abandoned_by_misnesting = True
         for child in self._children:
             child._mark_abandoned()
@@ -494,7 +502,7 @@ class CancelScope(metaclass=Final):
     _shield: bool = attr.ib(default=False, kw_only=True)
 
     @enable_ki_protection
-    def __enter__(self):
+    def __enter__(self) -> Self:
         task = _core.current_task()
         if self._has_been_entered:
             raise RuntimeError(
@@ -508,7 +516,7 @@ def __enter__(self):
             task._activate_cancel_status(self._cancel_status)
         return self
 
-    def _close(self, exc):
+    def _close(self, exc: BaseException | None) -> BaseException | None:
         if self._cancel_status is None:
             new_exc = RuntimeError(
                 "Cancel scope stack corrupted: attempted to exit {!r} "
@@ -810,8 +818,10 @@ def started(self, value: StatusT_contra | None = None) -> None:
 class _TaskStatus(TaskStatus[StatusT]):
     _old_nursery: Nursery = attr.ib()
     _new_nursery: Nursery = attr.ib()
+    # _called_started indicates whether _value can be accessed. The latter can't be Optional[],
+    # since None is a valid value too.
     _called_started: bool = attr.ib(default=False)
-    _value: StatusT | None = attr.ib(default=None)
+    _value: StatusT = attr.ib(default=cast(StatusT, None))
 
     def __repr__(self) -> str:
         return f"<Task status object at {id(self):#x}>"
@@ -824,7 +834,7 @@ def started(self: TaskStatus[None]) -> None:
     def started(self, value: StatusT) -> None:
         ...
 
-    def started(self, value: StatusT | None = None) -> None:
+    def started(self, value: StatusT = None) -> None:  # type: ignore[assignment] # StatusT != None
         if self._called_started:
             raise RuntimeError("called 'started' twice on the same task status")
         self._called_started = True
@@ -880,7 +890,9 @@ def started(self, value: StatusT | None = None) -> None:
 class _NurseryStartFunc(Protocol[StatusT_co]):
     """Type of functions passed to Nursery.start()."""
 
-    def __call__(self, *args: Any, task_status: TaskStatus[StatusT_co]) -> object:
+    def __call__(
+        self, *args: Any, task_status: TaskStatus[StatusT_co]
+    ) -> Awaitable[object]:
         ...
 
 
@@ -1090,6 +1102,7 @@ def aborted(raise_cancel):
 
     def start_soon(
         self,
+        # TODO: TypeVarTuple
         async_fn: Callable[..., Awaitable[object]],
         *args: Any,
         name: object = None,
@@ -1335,9 +1348,9 @@ def print_stack_for_task(task):
 
     # The CancelStatus object that is currently active for this task.
     # Don't change this directly; instead, use _activate_cancel_status().
-    _cancel_status: CancelStatus = attr.ib(default=None, repr=False)
+    _cancel_status: CancelStatus | None = attr.ib(default=None, repr=False)
 
-    def _activate_cancel_status(self, cancel_status: CancelStatus) -> None:
+    def _activate_cancel_status(self, cancel_status: CancelStatus | None) -> None:
         if self._cancel_status is not None:
             self._cancel_status._tasks.remove(self)
         self._cancel_status = cancel_status
@@ -1476,44 +1489,44 @@ def in_main_thread():
 
 @attr.s(eq=False, hash=False, slots=True)
 class Runner:
-    clock = attr.ib()
+    clock: trio._abc.Clock = attr.ib()
     instruments: Instruments = attr.ib()
-    io_manager = attr.ib()
-    ki_manager = attr.ib()
-    strict_exception_groups = attr.ib()
+    io_manager: TheIOManager = attr.ib()
+    ki_manager: KIManager = attr.ib()
+    strict_exception_groups: bool = attr.ib()
 
     # Run-local values, see _local.py
     _locals = attr.ib(factory=dict)
 
     runq: deque[Task] = attr.ib(factory=deque)
-    tasks = attr.ib(factory=set)
+    tasks: set[Task] = attr.ib(factory=set)
 
     deadlines = attr.ib(factory=Deadlines)
 
-    init_task = attr.ib(default=None)
+    init_task: Task | None = attr.ib(default=None)
     system_nursery = attr.ib(default=None)
     system_context = attr.ib(default=None)
     main_task = attr.ib(default=None)
     main_task_outcome = attr.ib(default=None)
 
-    entry_queue = attr.ib(factory=EntryQueue)
-    trio_token = attr.ib(default=None)
-    asyncgens = attr.ib(factory=AsyncGenerators)
+    entry_queue: EntryQueue = attr.ib(factory=EntryQueue)
+    trio_token: TrioToken | None = attr.ib(default=None)
+    asyncgens: AsyncGenerators = attr.ib(factory=AsyncGenerators)
 
     # If everything goes idle for this long, we call clock._autojump()
     clock_autojump_threshold = attr.ib(default=inf)
 
     # Guest mode stuff
-    is_guest = attr.ib(default=False)
-    guest_tick_scheduled = attr.ib(default=False)
+    is_guest: bool = attr.ib(default=False)
+    guest_tick_scheduled: bool = attr.ib(default=False)
 
-    def force_guest_tick_asap(self):
+    def force_guest_tick_asap(self) -> None:
         if self.guest_tick_scheduled:
             return
         self.guest_tick_scheduled = True
         self.io_manager.force_wakeup()
 
-    def close(self):
+    def close(self) -> None:
         self.io_manager.close()
         self.entry_queue.close()
         self.asyncgens.close()
@@ -1556,7 +1569,7 @@ def current_statistics(self):
         )
 
     @_public
-    def current_time(self):
+    def current_time(self) -> float:
         """Returns the current time according to Trio's internal clock.
 
         Returns:
@@ -1569,12 +1582,12 @@ def current_time(self):
         return self.clock.current_time()
 
     @_public
-    def current_clock(self):
+    def current_clock(self) -> trio._abc.Clock:
         """Returns the current :class:`~trio.abc.Clock`."""
         return self.clock
 
     @_public
-    def current_root_task(self):
+    def current_root_task(self) -> trio.lowlevel.Task | None:
         """Returns the current root :class:`Task`.
 
         This is the task that is the ultimate parent of all other tasks.
@@ -1587,7 +1600,9 @@ def current_root_task(self):
     ################
 
     @_public
-    def reschedule(self, task, next_send=_NO_SEND):
+    def reschedule(
+        self, task: trio.lowlevel.Task, next_send: object = _NO_SEND
+    ) -> None:
         """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 
@@ -1621,8 +1636,16 @@ def reschedule(self, task, next_send=_NO_SEND):
             self.instruments.call("task_scheduled", task)
 
     def spawn_impl(
-        self, async_fn, args, nursery, name, *, system_task=False, context=None
-    ):
+        self,
+        # TODO: TypeVarTuple
+        async_fn: Callable[..., Awaitable[object]],
+        args: tuple[Any, ...],
+        nursery: Nursery | None,
+        name: object,
+        *,
+        system_task: bool = False,
+        context: contextvars.Context | None = None,
+    ) -> Task:
         ######
         # Make sure the nursery is in working order
         ######
@@ -1660,7 +1683,7 @@ def spawn_impl(
             name = name.func
         if not isinstance(name, str):
             try:
-                name = f"{name.__module__}.{name.__qualname__}"
+                name = f"{name.__module__}.{name.__qualname__}"  # type: ignore[attr-defined]
             except AttributeError:
                 name = repr(name)
 
@@ -1691,7 +1714,7 @@ async def python_wrapper(orig_coro):
         self.reschedule(task, None)
         return task
 
-    def task_exited(self, task, outcome):
+    def task_exited(self, task: Task, outcome: Outcome) -> None:
         if (
             task._cancel_status is not None
             and task._cancel_status.abandoned_by_misnesting
@@ -1740,7 +1763,14 @@ def task_exited(self, task, outcome):
     ################
 
     @_public
-    def spawn_system_task(self, async_fn, *args, name=None, context=None):
+    def spawn_system_task(
+        self,
+        # TODO: TypeVarTuple
+        async_fn: Callable[..., Awaitable[object]],
+        *args: Any,
+        name: object = None,
+        context: contextvars.Context | None = None,
+    ) -> trio.lowlevel.Task:
         """Spawn a "system" task.
 
         System tasks have a few differences from regular tasks:
@@ -1801,7 +1831,12 @@ def spawn_system_task(self, async_fn, *args, name=None, context=None):
             context=context,
         )
 
-    async def init(self, async_fn, args):
+    async def init(
+        # TODO: TypeVarTuple
+        self,
+        async_fn: Callable[..., Awaitable[object]],
+        args: tuple[Any, ...],
+    ) -> None:
         # run_sync_soon task runs here:
         async with open_nursery() as run_sync_soon_nursery:
             # All other system tasks run here:
@@ -1839,7 +1874,7 @@ async def init(self, async_fn, args):
     ################
 
     @_public
-    def current_trio_token(self):
+    def current_trio_token(self) -> trio.lowlevel.TrioToken:
         """Retrieve the :class:`TrioToken` for the current call to
         :func:`trio.run`.
 
@@ -1888,7 +1923,7 @@ def _deliver_ki_cb(self):
     waiting_for_idle = attr.ib(factory=SortedDict)
 
     @_public
-    async def wait_all_tasks_blocked(self, cushion=0.0):
+    async def wait_all_tasks_blocked(self, cushion: float = 0.0) -> None:
         """Block until there are no runnable tasks.
 
         This is useful in testing code when you want to give other tasks a
@@ -2399,7 +2434,7 @@ def unrolled_run(
                 next_send_fn = task._next_send_fn
                 next_send = task._next_send
                 task._next_send_fn = task._next_send = None
-                final_outcome = None
+                final_outcome: Outcome | None = None
                 try:
                     # We used to unwrap the Outcome object here and send/throw
                     # its contents in directly, but it turns out that .throw()

From 39717d33fa25a9d116fb2fa9b0acab21203a6133 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 29 Jul 2023 13:38:52 +1000
Subject: [PATCH 06/56] TaskStatus is now generic, this works

---
 trio/_dtls.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/trio/_dtls.py b/trio/_dtls.py
index 8675cb75b6..69885ac001 100644
--- a/trio/_dtls.py
+++ b/trio/_dtls.py
@@ -1267,7 +1267,7 @@ async def serve(
         ssl_context: Context,
         async_fn: Callable[..., Awaitable[object]],
         *args: Any,
-        task_status: TaskStatus = trio.TASK_STATUS_IGNORED,  # type: ignore[has-type]
+        task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
     ) -> None:
         """Listen for incoming connections, and spawn a handler for each using an
         internal nursery.

From ea8a1ecb7006e7bc75164207ccc7561b9a39bff2 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 29 Jul 2023 13:39:20 +1000
Subject: [PATCH 07/56] Tweak the generated module imports to allow these hints
 to work

---
 trio/_core/_generated_instrumentation.py | 10 ++++++++--
 trio/_core/_generated_io_epoll.py        | 10 ++++++++--
 trio/_core/_generated_io_kqueue.py       | 10 ++++++++--
 trio/_core/_generated_io_windows.py      | 10 ++++++++--
 trio/_core/_generated_run.py             | 24 ++++++++++++++++--------
 trio/_tools/gen_exports.py               | 10 ++++++++--
 6 files changed, 56 insertions(+), 18 deletions(-)

diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index 30c2f26b4e..cac33b7444 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -1,10 +1,16 @@
 # ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+import trio
 
 # fmt: off
 
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index 02fb3bc348..5685e395fe 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -1,10 +1,16 @@
 # ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+import trio
 
 # fmt: off
 
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index 94e819769c..e399fa9087 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -1,10 +1,16 @@
 # ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+import trio
 
 # fmt: off
 
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 26b4da697d..89553595bf 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -1,10 +1,16 @@
 # ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+import trio
 
 # fmt: off
 
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index d1e74a93f4..1ed7c1cd88 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -1,10 +1,16 @@
 # ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
 from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+import trio
 
 # fmt: off
 
@@ -39,7 +45,7 @@ def current_statistics():
         raise RuntimeError("must be called from async context")
 
 
-def current_time():
+def current_time() ->float:
     """Returns the current time according to Trio's internal clock.
 
         Returns:
@@ -56,7 +62,7 @@ def current_time():
         raise RuntimeError("must be called from async context")
 
 
-def current_clock():
+def current_clock() ->trio._abc.Clock:
     """Returns the current :class:`~trio.abc.Clock`."""
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
     try:
@@ -65,7 +71,7 @@ def current_clock():
         raise RuntimeError("must be called from async context")
 
 
-def current_root_task():
+def current_root_task() ->(trio.lowlevel.Task | None):
     """Returns the current root :class:`Task`.
 
         This is the task that is the ultimate parent of all other tasks.
@@ -78,7 +84,7 @@ def current_root_task():
         raise RuntimeError("must be called from async context")
 
 
-def reschedule(task, next_send=_NO_SEND):
+def reschedule(task: trio.lowlevel.Task, next_send: object=_NO_SEND) ->None:
     """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 
@@ -103,7 +109,9 @@ def reschedule(task, next_send=_NO_SEND):
         raise RuntimeError("must be called from async context")
 
 
-def spawn_system_task(async_fn, *args, name=None, context=None):
+def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
+    Any, name: object=None, context: (contextvars.Context | None)=None
+    ) ->trio.lowlevel.Task:
     """Spawn a "system" task.
 
         System tasks have a few differences from regular tasks:
@@ -162,7 +170,7 @@ def spawn_system_task(async_fn, *args, name=None, context=None):
         raise RuntimeError("must be called from async context")
 
 
-def current_trio_token():
+def current_trio_token() ->trio.lowlevel.TrioToken:
     """Retrieve the :class:`TrioToken` for the current call to
         :func:`trio.run`.
 
@@ -174,7 +182,7 @@ def current_trio_token():
         raise RuntimeError("must be called from async context")
 
 
-async def wait_all_tasks_blocked(cushion=0.0):
+async def wait_all_tasks_blocked(cushion: float=0.0) ->None:
     """Block until there are no runnable tasks.
 
         This is useful in testing code when you want to give other tasks a
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index a5d8529b53..229a03d974 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -17,10 +17,16 @@
 HEADER = """# ***********************************************************
 # ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
 # *************************************************************
-# isort: skip
+# isort: skip_file
+from __future__ import annotations
+
+from typing import Any, Callable, Awaitable
+import contextvars
+
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+import trio
 
 # fmt: off
 """

From 3de39202aa3da6ec0b0ccd6dee2ed9c39ac26744 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 3 Aug 2023 18:01:33 +1000
Subject: [PATCH 08/56] Run linters

---
 docs/source/conf.py                      |  1 +
 trio/_core/_generated_instrumentation.py |  2 +-
 trio/_core/_generated_io_epoll.py        |  2 +-
 trio/_core/_generated_io_kqueue.py       |  2 +-
 trio/_core/_generated_io_windows.py      |  2 +-
 trio/_core/_run.py                       |  4 ++--
 trio/_tests/verify_types.json            | 23 +++++------------------
 trio/_tools/gen_exports.py               |  2 +-
 8 files changed, 13 insertions(+), 25 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 224ad507bf..3a3b414b27 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -63,6 +63,7 @@
     ("py:obj", "trio._abc.T"),
     ("py:obj", "trio._abc.T_resource"),
     ("py:class", "trio._core._run.StatusT"),
+    ("py:class", "trio._core._run.StatusT_contra"),
     ("py:class", "trio._threads.T"),
     # why aren't these found in stdlib?
     ("py:class", "types.FrameType"),
diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index cac33b7444..aa709b9f58 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -9,7 +9,7 @@
 
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
 import trio
 
 # fmt: off
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index 5685e395fe..7cddd6e1e7 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -9,7 +9,7 @@
 
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
 import trio
 
 # fmt: off
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index e399fa9087..0820ec178d 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -9,7 +9,7 @@
 
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
 import trio
 
 # fmt: off
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 89553595bf..d26a98e49e 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -9,7 +9,7 @@
 
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
 import trio
 
 # fmt: off
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 95e780bab9..d22ee4dd40 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -887,7 +887,7 @@ def started(self, value: StatusT = None) -> None:  # type: ignore[assignment] #
         self._old_nursery._check_nursery_closed()
 
 
-class _NurseryStartFunc(Protocol[StatusT_co]):
+class NurseryStartFunc(Protocol[StatusT_co]):
     """Type of functions passed to Nursery.start()."""
 
     def __call__(
@@ -1149,7 +1149,7 @@ def start_soon(
         GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)
 
     async def start(
-        self, async_fn: _NurseryStartFunc[StatusT], *args: Any, name: object = None
+        self, async_fn: NurseryStartFunc[StatusT], *args: Any, name: object = None
     ) -> StatusT:
         r"""Creates and initializes a child task.
 
diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index ac2cfbd197..2160ae19ad 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -7,11 +7,11 @@
     "warningCount": 0
   },
   "typeCompleteness": {
-    "completenessScore": 0.9154704944178629,
+    "completenessScore": 0.9330143540669856,
     "exportedSymbolCounts": {
       "withAmbiguousType": 0,
-      "withKnownType": 574,
-      "withUnknownType": 53
+      "withKnownType": 585,
+      "withUnknownType": 42
     },
     "ignoreUnknownTypesFromImports": true,
     "missingClassDocStringCount": 1,
@@ -46,18 +46,12 @@
     ],
     "otherSymbolCounts": {
       "withAmbiguousType": 3,
-      "withKnownType": 600,
-      "withUnknownType": 63
+      "withKnownType": 601,
+      "withUnknownType": 52
     },
     "packageName": "trio",
     "symbols": [
       "trio._core._mock_clock.MockClock.jump",
-      "trio._core._run.Nursery.start",
-      "trio._core._run.Nursery.start_soon",
-      "trio._core._run.TaskStatus.__repr__",
-      "trio._core._run.TaskStatus.started",
-      "trio._dtls.DTLSChannel.__init__",
-      "trio._dtls.DTLSEndpoint.serve",
       "trio._highlevel_socket.SocketStream.getsockopt",
       "trio._highlevel_socket.SocketStream.send_all",
       "trio._highlevel_socket.SocketStream.setsockopt",
@@ -87,20 +81,14 @@
       "trio._subprocess.Process.send_signal",
       "trio._subprocess.Process.terminate",
       "trio._subprocess.Process.wait",
-      "trio.current_time",
       "trio.from_thread.run",
       "trio.from_thread.run_sync",
       "trio.lowlevel.cancel_shielded_checkpoint",
-      "trio.lowlevel.current_clock",
-      "trio.lowlevel.current_root_task",
       "trio.lowlevel.current_statistics",
-      "trio.lowlevel.current_trio_token",
       "trio.lowlevel.notify_closing",
       "trio.lowlevel.open_process",
       "trio.lowlevel.permanently_detach_coroutine_object",
       "trio.lowlevel.reattach_detached_coroutine_object",
-      "trio.lowlevel.reschedule",
-      "trio.lowlevel.spawn_system_task",
       "trio.lowlevel.start_guest_run",
       "trio.lowlevel.start_thread_soon",
       "trio.lowlevel.temporarily_detach_coroutine_object",
@@ -145,7 +133,6 @@
       "trio.testing.memory_stream_pump",
       "trio.testing.open_stream_to_socket_listener",
       "trio.testing.trio_test",
-      "trio.testing.wait_all_tasks_blocked",
       "trio.tests.TestsDeprecationWrapper",
       "trio.to_thread.current_default_thread_limiter"
     ]
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 229a03d974..64ee789d3e 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -25,7 +25,7 @@
 
 from ._instrumentation import Instrument
 from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT 
+from ._run import _NO_SEND, GLOBAL_RUN_CONTEXT
 import trio
 
 # fmt: off

From f84d4a10156541a50632352ba33ac514cb12bf77 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 3 Aug 2023 18:21:24 +1000
Subject: [PATCH 09/56] Ignore 3.12+ protocol internals

---
 trio/_tests/test_exports.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py
index 682d5f9c49..acf2e70bc8 100644
--- a/trio/_tests/test_exports.py
+++ b/trio/_tests/test_exports.py
@@ -324,6 +324,8 @@ def lookup_symbol(symbol):
             "__order__",
             "__orig_bases__",
             "__parameters__",
+            "__callable_proto_members_only__",
+            "__protocol_attrs__",
             "__setstate__",
             "__slots__",
             "__weakref__",

From 1c5d4232210de4b492e7a14444722d13e348fe36 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 3 Aug 2023 18:35:42 +1000
Subject: [PATCH 10/56] Ignore protocols in coverage

---
 .coveragerc                 | 1 +
 trio/_tests/test_exports.py | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/.coveragerc b/.coveragerc
index 431a02971b..5272237caf 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -23,6 +23,7 @@ exclude_lines =
   if _t.TYPE_CHECKING:
   if t.TYPE_CHECKING:
   @overload
+  class .*\bProtocol\b.*\):
 
 partial_branches =
   pragma: no branch
diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py
index acf2e70bc8..2b7a3947f2 100644
--- a/trio/_tests/test_exports.py
+++ b/trio/_tests/test_exports.py
@@ -26,7 +26,7 @@
 
 try:  # If installed, check both versions of this class.
     from typing_extensions import Protocol as Protocol_ext
-except ImportError:
+except ImportError:  # pragma: no cover
     Protocol_ext = Protocol
 
 

From 4c74914524df3e3e5276b37eec381feb7bd681fd Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 09:21:36 +1000
Subject: [PATCH 11/56] This actually checks that the awaitable is a coroutine

---
 trio/_util.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/trio/_util.py b/trio/_util.py
index ba56c18385..0329a12fa9 100644
--- a/trio/_util.py
+++ b/trio/_util.py
@@ -102,7 +102,7 @@ def is_main_thread() -> bool:
 # TODO: Use TypeVarTuple here.
 def coroutine_or_error(
     async_fn: t.Callable[..., t.Awaitable[RetT]], *args: t.Any
-) -> t.Awaitable[RetT]:
+) -> collections.abc.Coroutine[object, t.NoReturn, RetT]:
     def _return_value_looks_like_wrong_library(value: object) -> bool:
         # Returned by legacy @asyncio.coroutine functions, which includes
         # a surprising proportion of asyncio builtins.

From bc4b67012d5b392b2e9183837fadb0bcab2d9f02 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 09:24:07 +1000
Subject: [PATCH 12/56] Add asserts, mainly around task._cancel_status not
 being None.

---
 trio/_core/_run.py | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index d22ee4dd40..3d571006e7 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -10,7 +10,7 @@
 import threading
 import warnings
 from collections import deque
-from collections.abc import Awaitable, Callable, Coroutine, Iterator
+from collections.abc import Coroutine, Iterator
 from contextlib import AbstractAsyncContextManager, contextmanager
 from contextvars import copy_context
 from heapq import heapify, heappop, heappush
@@ -554,12 +554,17 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
                 # CancelStatus.close() will take care of the plumbing;
                 # we just need to make sure we don't let the error
                 # pass silently.
+                scope = (
+                    repr(scope_task._cancel_status._scope)
+                    if scope_task._cancel_status is not None
+                    else "<no scope??>"
+                )
                 new_exc = RuntimeError(
                     "Cancel scope stack corrupted: attempted to exit {!r} "
-                    "in {!r} that's still within its child {!r}\n{}".format(
+                    "in {!r} that's still within its child {}\n{}".format(
                         self,
                         scope_task,
-                        scope_task._cancel_status._scope,
+                        scope,
                         MISNESTING_ADVICE,
                     )
                 )
@@ -844,6 +849,7 @@ def started(self, value: StatusT = None) -> None:  # type: ignore[assignment] #
         # will eventually exit on its own, and we don't want to risk moving
         # children that might have propagating Cancelled exceptions into
         # a place with no cancelled cancel scopes to catch them.
+        assert self._old_nursery._cancel_status is not None
         if self._old_nursery._cancel_status.effectively_cancelled:
             return
 
@@ -1381,10 +1387,11 @@ def _attempt_abort(self, raise_cancel: Callable[[], NoReturn]) -> None:
     def _attempt_delivery_of_any_pending_cancel(self) -> None:
         if self._abort_func is None:
             return
+        assert self._cancel_status is not None
         if not self._cancel_status.effectively_cancelled:
             return
 
-        def raise_cancel():
+        def raise_cancel() -> NoReturn:
             raise Cancelled._create()
 
         self._attempt_abort(raise_cancel)
@@ -1638,7 +1645,7 @@ def reschedule(
     def spawn_impl(
         self,
         # TODO: TypeVarTuple
-        async_fn: Callable[..., Awaitable[object]],
+        async_fn: Callable[..., Awaitable[Any]],
         args: tuple[Any, ...],
         nursery: Nursery | None,
         name: object,
@@ -1753,6 +1760,7 @@ def task_exited(self, task: Task, outcome: Outcome) -> None:
             if task is self.main_task:
                 self.main_task_outcome = outcome
                 outcome = Value(None)
+            assert task._parent_nursery is not None, task
             task._parent_nursery._child_finished(task, outcome)
 
         if "task_exited" in self.instruments:
@@ -2390,6 +2398,7 @@ def unrolled_run(
                             break
                 else:
                     assert idle_primed is IdlePrimedTypes.AUTOJUMP_CLOCK
+                    assert isinstance(runner.clock, _core.MockClock)
                     runner.clock._autojump()
 
             # Process all runnable tasks, but only the ones that are already
@@ -2590,7 +2599,9 @@ def current_effective_deadline() -> float:
         float: the effective deadline, as an absolute time.
 
     """
-    return current_task()._cancel_status.effective_deadline()
+    status = current_task()._cancel_status
+    assert status is not None
+    return status.effective_deadline()
 
 
 async def checkpoint() -> None:
@@ -2613,6 +2624,7 @@ async def checkpoint() -> None:
     await cancel_shielded_checkpoint()
     task = current_task()
     task._cancel_points += 1
+    assert task._cancel_status is not None, task
     if task._cancel_status.effectively_cancelled or (
         task is task._runner.main_task and task._runner.ki_pending
     ):
@@ -2636,6 +2648,7 @@ async def checkpoint_if_cancelled() -> None:
 
     """
     task = current_task()
+    assert task._cancel_status is not None, task
     if task._cancel_status.effectively_cancelled or (
         task is task._runner.main_task and task._runner.ki_pending
     ):

From 818a137ee9a863716d6939a9ee845bae9410dcb5 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 09:32:10 +1000
Subject: [PATCH 13/56] Restrict the type for Runner.reschedule(), add
 type-ignores for the two exceptions

---
 trio/_core/_generated_instrumentation.py | 1 +
 trio/_core/_generated_io_epoll.py        | 1 +
 trio/_core/_generated_io_kqueue.py       | 1 +
 trio/_core/_generated_io_windows.py      | 1 +
 trio/_core/_generated_run.py             | 3 ++-
 trio/_core/_run.py                       | 8 ++++----
 trio/_tools/gen_exports.py               | 1 +
 7 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index aa709b9f58..cf9a6aec65 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index 7cddd6e1e7..132ed51d81 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index 0820ec178d..f87d9d77aa 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index d26a98e49e..8a7d29240f 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index 1ed7c1cd88..3a6b26395f 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument
@@ -84,7 +85,7 @@ def current_root_task() ->(trio.lowlevel.Task | None):
         raise RuntimeError("must be called from async context")
 
 
-def reschedule(task: trio.lowlevel.Task, next_send: object=_NO_SEND) ->None:
+def reschedule(task: trio.lowlevel.Task, next_send: Outcome=_NO_SEND) ->None:
     """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 3d571006e7..5f2fde6950 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1249,7 +1249,7 @@ class Task(metaclass=NoPublicConstructor):
     # - for scheduled tasks, custom_sleep_data is None
     # Tasks start out unscheduled.
     _next_send_fn = attr.ib(default=None)
-    _next_send = attr.ib(default=None)
+    _next_send: Outcome | None | BaseException = attr.ib(default=None)
     _abort_func: Callable[[Callable[[], NoReturn]], Abort] | None = attr.ib(
         default=None
     )
@@ -1608,7 +1608,7 @@ def current_root_task(self) -> trio.lowlevel.Task | None:
 
     @_public
     def reschedule(
-        self, task: trio.lowlevel.Task, next_send: object = _NO_SEND
+        self, task: trio.lowlevel.Task, next_send: Outcome = _NO_SEND
     ) -> None:
         """Reschedule the given task with the given
         :class:`outcome.Outcome`.
@@ -1718,7 +1718,7 @@ async def python_wrapper(orig_coro):
             self.instruments.call("task_spawned", task)
         # Special case: normally next_send should be an Outcome, but for the
         # very first send we have to send a literal unboxed None.
-        self.reschedule(task, None)
+        self.reschedule(task, None)  # type: ignore[arg-type]
         return task
 
     def task_exited(self, task: Task, outcome: Outcome) -> None:
@@ -2507,7 +2507,7 @@ def unrolled_run(
                         # protocol of unwrapping whatever outcome gets sent in.
                         # Instead, we'll arrange to throw `exc` in directly,
                         # which works for at least asyncio and curio.
-                        runner.reschedule(task, exc)
+                        runner.reschedule(task, exc)  # type: ignore[arg-type]
                         task._next_send_fn = task.coro.throw
                     # prevent long-lived reference
                     # TODO: develop test for this deletion
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 64ee789d3e..3f1686e8e9 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -21,6 +21,7 @@
 from __future__ import annotations
 
 from typing import Any, Callable, Awaitable
+from outcome import Outcome
 import contextvars
 
 from ._instrumentation import Instrument

From 80fe5009a8d473bbf6d0900a9679063f95ef1549 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 09:59:38 +1000
Subject: [PATCH 14/56] Fill in various types

---
 trio/_core/_run.py | 74 ++++++++++++++++++++++++----------------------
 1 file changed, 39 insertions(+), 35 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 5f2fde6950..da5d69016c 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -10,7 +10,7 @@
 import threading
 import warnings
 from collections import deque
-from collections.abc import Coroutine, Iterator
+from collections.abc import Coroutine, Generator, Iterator
 from contextlib import AbstractAsyncContextManager, contextmanager
 from contextvars import copy_context
 from heapq import heapify, heappop, heappush
@@ -55,12 +55,12 @@
     # An unfortunate name collision here with trio._util.Final
     from typing import Final as FinalT
 
-    from typing_extensions import Self
+    from typing_extensions import Literal, Self
 
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 
 # Passed as a sentinel
-_NO_SEND: Outcome = cast(Outcome, object())
+_NO_SEND: Outcome[Any] = cast(Outcome, object())
 
 FnT = TypeVar("FnT", bound="Callable[..., Any]")
 StatusT = TypeVar("StatusT")
@@ -1054,20 +1054,20 @@ def _add_exc(self, exc: BaseException) -> None:
         self._pending_excs.append(exc)
         self.cancel_scope.cancel()
 
-    def _check_nursery_closed(self):
+    def _check_nursery_closed(self) -> None:
         if not any([self._nested_child_running, self._children, self._pending_starts]):
             self._closed = True
             if self._parent_waiting_in_aexit:
                 self._parent_waiting_in_aexit = False
                 GLOBAL_RUN_CONTEXT.runner.reschedule(self._parent_task)
 
-    def _child_finished(self, task, outcome):
+    def _child_finished(self, task: Task, outcome: Outcome[Any]) -> None:
         self._children.remove(task)
         if isinstance(outcome, Error):
             self._add_exc(outcome.error)
         self._check_nursery_closed()
 
-    async def _nested_child_finished(self, nested_child_exc):
+    async def _nested_child_finished(self, nested_child_exc: BaseException) -> BaseException | None:
         # Returns MultiError instance (or any exception if the nursery is in loose mode
         # and there is just one contained exception) if there are pending exceptions
         if nested_child_exc is not None:
@@ -1079,7 +1079,7 @@ async def _nested_child_finished(self, nested_child_exc):
             # If we get cancelled (or have an exception injected, like
             # KeyboardInterrupt), then save that, but still wait until our
             # children finish.
-            def aborted(raise_cancel):
+            def aborted(raise_cancel: Callable[[], NoReturn]) -> Abort:
                 self._add_exc(capture(raise_cancel).error)
                 return Abort.FAILED
 
@@ -1232,7 +1232,7 @@ def __del__(self) -> None:
 class Task(metaclass=NoPublicConstructor):
     _parent_nursery: Nursery | None = attr.ib()
     coro: Coroutine[Any, Outcome[object], Any] = attr.ib()
-    _runner = attr.ib()
+    _runner: Runner = attr.ib()
     name: str = attr.ib()
     context: contextvars.Context = attr.ib()
     _counter: int = attr.ib(init=False, factory=itertools.count().__next__)
@@ -1248,8 +1248,8 @@ class Task(metaclass=NoPublicConstructor):
     #   tracebacks with extraneous frames.
     # - for scheduled tasks, custom_sleep_data is None
     # Tasks start out unscheduled.
-    _next_send_fn = attr.ib(default=None)
-    _next_send: Outcome | None | BaseException = attr.ib(default=None)
+    _next_send_fn: Callable[[Any], object] = attr.ib(default=None)
+    _next_send: Outcome[Any] | None | BaseException = attr.ib(default=None)
     _abort_func: Callable[[Callable[[], NoReturn]], Abort] | None = attr.ib(
         default=None
     )
@@ -1420,15 +1420,19 @@ class RunContext(threading.local):
 
 GLOBAL_RUN_CONTEXT: FinalT = RunContext()
 
+class _IOStats(Protocol):
+    @property
+    def backend(self) -> str:
+        ...
+
 
 @attr.s(frozen=True)
 class _RunStatistics:
-    tasks_living = attr.ib()
-    tasks_runnable = attr.ib()
-    seconds_to_next_deadline = attr.ib()
-    io_statistics = attr.ib()
-    run_sync_soon_queue_size = attr.ib()
-
+    tasks_living: int = attr.ib()
+    tasks_runnable: int = attr.ib()
+    seconds_to_next_deadline: float = attr.ib()
+    io_statistics: _IOStats = attr.ib()
+    run_sync_soon_queue_size: int = attr.ib()
 
 # This holds all the state that gets trampolined back and forth between
 # callbacks when we're running in guest mode.
@@ -1451,15 +1455,15 @@ class _RunStatistics:
 # worker thread.
 @attr.s(eq=False, hash=False, slots=True)
 class GuestState:
-    runner = attr.ib()
-    run_sync_soon_threadsafe = attr.ib()
-    run_sync_soon_not_threadsafe = attr.ib()
-    done_callback = attr.ib()
-    unrolled_run_gen = attr.ib()
-    _value_factory: Callable[[], Value] = lambda: Value(None)
-    unrolled_run_next_send = attr.ib(factory=_value_factory, type=Outcome)
-
-    def guest_tick(self):
+    runner: Runner = attr.ib()
+    run_sync_soon_threadsafe: Callable[[Callable[[], object]], object] = attr.ib()
+    run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object] = attr.ib()
+    done_callback: Callable[[Outcome[Any]], object] = attr.ib()
+    unrolled_run_gen: Generator[None, None, None] = attr.ib()
+    _value_factory: Callable[[], Value[Any]] = lambda: Value(None)
+    unrolled_run_next_send: Outcome[Any] = attr.ib(factory=_value_factory, type=Outcome)
+
+    def guest_tick(self) -> None:
         try:
             timeout = self.unrolled_run_next_send.send(self.unrolled_run_gen)
         except StopIteration:
@@ -1483,8 +1487,8 @@ def guest_tick(self):
             def get_events():
                 return self.runner.io_manager.get_events(timeout)
 
-            def deliver(events_outcome):
-                def in_main_thread():
+            def deliver(events_outcome: Outcome) -> None:
+                def in_main_thread() -> None:
                     self.unrolled_run_next_send = events_outcome
                     self.runner.guest_tick_scheduled = True
                     self.guest_tick()
@@ -1508,20 +1512,20 @@ class Runner:
     runq: deque[Task] = attr.ib(factory=deque)
     tasks: set[Task] = attr.ib(factory=set)
 
-    deadlines = attr.ib(factory=Deadlines)
+    deadlines: Deadlines = attr.ib(factory=Deadlines)
 
     init_task: Task | None = attr.ib(default=None)
-    system_nursery = attr.ib(default=None)
+    system_nursery: Nursery | None = attr.ib(default=None)
     system_context = attr.ib(default=None)
-    main_task = attr.ib(default=None)
-    main_task_outcome = attr.ib(default=None)
+    main_task: Task | None = attr.ib(default=None)
+    main_task_outcome: Outcome[Any] | None = attr.ib(default=None)
 
     entry_queue: EntryQueue = attr.ib(factory=EntryQueue)
     trio_token: TrioToken | None = attr.ib(default=None)
     asyncgens: AsyncGenerators = attr.ib(factory=AsyncGenerators)
 
     # If everything goes idle for this long, we call clock._autojump()
-    clock_autojump_threshold = attr.ib(default=inf)
+    clock_autojump_threshold: float = attr.ib(default=inf)
 
     # Guest mode stuff
     is_guest: bool = attr.ib(default=False)
@@ -1543,10 +1547,10 @@ def close(self) -> None:
         self.ki_manager.close()
 
     @_public
-    def current_statistics(self):
+    def current_statistics(self) -> _RunStatistics:
         """Returns an object containing run-loop-level debugging information.
 
-        Currently the following fields are defined:
+        Currently, the following fields are defined:
 
         * ``tasks_living`` (int): The number of tasks that have been spawned
           and not yet exited.
@@ -2312,7 +2316,7 @@ def unrolled_run(
     async_fn,
     args,
     host_uses_signal_set_wakeup_fd: bool = False,
-):
+) -> None:
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
     __tracebackhide__ = True
 

From c900134f866006aba3e3b73c7345d245a3b81bac Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 09:59:15 +1000
Subject: [PATCH 15/56] Add type for Runner._locals

---
 trio/_core/_local.py | 11 +++++------
 trio/_core/_run.py   |  2 +-
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/trio/_core/_local.py b/trio/_core/_local.py
index 39a2cff009..8286a5578f 100644
--- a/trio/_core/_local.py
+++ b/trio/_core/_local.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar, final
+from typing import Generic, TypeVar, cast, final
 
 # Runvar implementations
 import attr
@@ -43,8 +43,7 @@ class RunVar(Generic[T], metaclass=Final):
     def get(self, default: T | type[_NoValue] = _NoValue) -> T:
         """Gets the value of this :class:`RunVar` for the current run call."""
         try:
-            # not typed yet
-            return _run.GLOBAL_RUN_CONTEXT.runner._locals[self]  # type: ignore[return-value, index]
+            return cast(T, _run.GLOBAL_RUN_CONTEXT.runner._locals[self])
         except AttributeError:
             raise RuntimeError("Cannot be used outside of a run context") from None
         except KeyError:
@@ -72,7 +71,7 @@ def set(self, value: T) -> RunVarToken[T]:
 
         # This can't fail, because if we weren't in Trio context then the
         # get() above would have failed.
-        _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = value  # type: ignore[assignment, index]
+        _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = value
         return token
 
     def reset(self, token: RunVarToken[T]) -> None:
@@ -92,9 +91,9 @@ def reset(self, token: RunVarToken[T]) -> None:
         previous = token.previous_value
         try:
             if previous is _NoValue:
-                _run.GLOBAL_RUN_CONTEXT.runner._locals.pop(self)  # type: ignore[arg-type]
+                _run.GLOBAL_RUN_CONTEXT.runner._locals.pop(self)
             else:
-                _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = previous  # type: ignore[index, assignment]
+                _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = previous
         except AttributeError:
             raise RuntimeError("Cannot be used outside of a run context")
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index da5d69016c..3e2312aa26 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1507,7 +1507,7 @@ class Runner:
     strict_exception_groups: bool = attr.ib()
 
     # Run-local values, see _local.py
-    _locals = attr.ib(factory=dict)
+    _locals: dict[_core.RunVar[Any], Any] = attr.ib(factory=dict)
 
     runq: deque[Task] = attr.ib(factory=deque)
     tasks: set[Task] = attr.ib(factory=set)

From 5a22c800681b5f644eb0b4dac2568e9ab797ddc2 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 10:15:01 +1000
Subject: [PATCH 16/56] Determine types for IOManager.get_events()

---
 trio/_core/_io_epoll.py  |  2 +-
 trio/_core/_io_kqueue.py |  2 +-
 trio/_core/_run.py       | 12 ++++++++----
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index 376dd18a4e..37f5b0385f 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -222,7 +222,7 @@ def force_wakeup(self):
     # Return value must be False-y IFF the timeout expired, NOT if any I/O
     # happened or force_wakeup was called. Otherwise it can be anything; gets
     # passed straight through to process_events.
-    def get_events(self, timeout):
+    def get_events(self, timeout) -> list[tuple[int, int]]:
         # max_events must be > 0 or epoll gets cranky
         # accessing self._registered from a thread looks dangerous, but it's
         # OK because it doesn't matter if our value is a little bit off.
diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index d1151843e8..bdfa91efa5 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -53,7 +53,7 @@ def close(self):
     def force_wakeup(self):
         self._force_wakeup.wakeup_thread_and_signal_safe()
 
-    def get_events(self, timeout):
+    def get_events(self, timeout: float) -> list[select.kevent]:
         # max_events must be > 0 or kqueue gets cranky
         # and we generally want this to be strictly larger than the actual
         # number of events we get, so that we can tell that we've gotten
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 3e2312aa26..7a3452086f 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -51,11 +51,15 @@
 
 if TYPE_CHECKING:
     import contextvars
+    import select
 
     # An unfortunate name collision here with trio._util.Final
     from typing import Final as FinalT
 
-    from typing_extensions import Literal, Self
+    from typing_extensions import Literal, Self, TypeAlias
+
+    # Result of IOManager.get_events() - Windows, Epoll, KQueue
+    EventResult: TypeAlias = 'int | list[tuple[int, int]] | list[select.kevent]'
 
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 
@@ -1474,7 +1478,7 @@ def guest_tick(self) -> None:
             return
 
         # Optimization: try to skip going into the thread if we can avoid it
-        events_outcome = capture(self.runner.io_manager.get_events, 0)
+        events_outcome: Value[EventResult] | Error = capture(self.runner.io_manager.get_events, 0)
         if timeout <= 0 or isinstance(events_outcome, Error) or events_outcome.value:
             # No need to go into the thread
             self.unrolled_run_next_send = events_outcome
@@ -1484,10 +1488,10 @@ def guest_tick(self) -> None:
             # Need to go into the thread and call get_events() there
             self.runner.guest_tick_scheduled = False
 
-            def get_events():
+            def get_events() -> EventResult:
                 return self.runner.io_manager.get_events(timeout)
 
-            def deliver(events_outcome: Outcome) -> None:
+            def deliver(events_outcome: Outcome[EventResult]) -> None:
                 def in_main_thread() -> None:
                     self.unrolled_run_next_send = events_outcome
                     self.runner.guest_tick_scheduled = True

From 86ea4c641e77314cf6d399a44a7fe48b1f2a598c Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 10:23:40 +1000
Subject: [PATCH 17/56] Make RunStatistics public

---
 docs/source/reference-lowlevel.rst |  6 +++++-
 trio/__init__.py                   |  1 +
 trio/_core/__init__.py             |  1 +
 trio/_core/_generated_run.py       |  6 +++---
 trio/_core/_run.py                 | 29 +++++++++++++++++++++++++----
 5 files changed, 35 insertions(+), 8 deletions(-)

diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst
index bacebff5ad..5418be30b5 100644
--- a/docs/source/reference-lowlevel.rst
+++ b/docs/source/reference-lowlevel.rst
@@ -49,7 +49,11 @@ attributes, :meth:`trio.Lock.statistics`, etc.). Here are some more.
 Global statistics
 -----------------
 
-.. autofunction:: current_statistics
+.. function:: current_statistics() -> RunStatistics
+
+   Returns an object containing run-loop-level debugging information:
+
+.. autoclass:: trio.RunStatistics()
 
 
 The current clock
diff --git a/trio/__init__.py b/trio/__init__.py
index be7de42cde..97efa368de 100644
--- a/trio/__init__.py
+++ b/trio/__init__.py
@@ -35,6 +35,7 @@
     EndOfChannel as EndOfChannel,
     Nursery as Nursery,
     RunFinishedError as RunFinishedError,
+    RunStatistics as RunStatistics,
     TaskStatus as TaskStatus,
     TrioInternalError as TrioInternalError,
     WouldBlock as WouldBlock,
diff --git a/trio/_core/__init__.py b/trio/_core/__init__.py
index 8e42d2743b..b9bd0d8cc4 100644
--- a/trio/_core/__init__.py
+++ b/trio/_core/__init__.py
@@ -27,6 +27,7 @@
     TASK_STATUS_IGNORED,
     CancelScope,
     Nursery,
+    RunStatistics,
     Task,
     TaskStatus,
     add_instrument,
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index 3a6b26395f..4d843bf0f6 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -16,10 +16,10 @@
 # fmt: off
 
 
-def current_statistics():
-    """Returns an object containing run-loop-level debugging information.
+def current_statistics() ->trio.RunStatistics:
+    """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
-        Currently the following fields are defined:
+        Currently, the following fields are defined:
 
         * ``tasks_living`` (int): The number of tasks that have been spawned
           and not yet exited.
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 7a3452086f..66563ebe16 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1431,7 +1431,28 @@ def backend(self) -> str:
 
 
 @attr.s(frozen=True)
-class _RunStatistics:
+class RunStatistics:
+    """An object containing run-loop-level debugging information.
+
+    Currently, the following fields are defined:
+
+    * ``tasks_living`` (int): The number of tasks that have been spawned
+      and not yet exited.
+    * ``tasks_runnable`` (int): The number of tasks that are currently
+      queued on the run queue (as opposed to blocked waiting for something
+      to happen).
+    * ``seconds_to_next_deadline`` (float): The time until the next
+      pending cancel scope deadline. May be negative if the deadline has
+      expired but we haven't yet processed cancellations. May be
+      :data:`~math.inf` if there are no pending deadlines.
+    * ``run_sync_soon_queue_size`` (int): The number of
+      unprocessed callbacks queued via
+      :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
+    * ``io_statistics`` (object): Some statistics from Trio's I/O
+      backend. This always has an attribute ``backend`` which is a string
+      naming which operating-system-specific I/O backend is in use; the
+      other attributes vary between backends.
+    """
     tasks_living: int = attr.ib()
     tasks_runnable: int = attr.ib()
     seconds_to_next_deadline: float = attr.ib()
@@ -1551,8 +1572,8 @@ def close(self) -> None:
         self.ki_manager.close()
 
     @_public
-    def current_statistics(self) -> _RunStatistics:
-        """Returns an object containing run-loop-level debugging information.
+    def current_statistics(self) -> trio.RunStatistics:
+        """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
         Currently, the following fields are defined:
 
@@ -1575,7 +1596,7 @@ def current_statistics(self) -> _RunStatistics:
 
         """
         seconds_to_next_deadline = self.deadlines.next_deadline() - self.current_time()
-        return _RunStatistics(
+        return RunStatistics(
             tasks_living=len(self.tasks),
             tasks_runnable=len(self.runq),
             seconds_to_next_deadline=seconds_to_next_deadline,

From 8b87f77b4af15535ce2de0ead94496dc031d4e1f Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 10:32:38 +1000
Subject: [PATCH 18/56] Use an imported type alias for get_events() instead

---
 trio/_core/_io_epoll.py   |  9 +++++++--
 trio/_core/_io_kqueue.py  |  9 +++++++--
 trio/_core/_io_windows.py |  9 +++++++--
 trio/_core/_run.py        | 10 +++-------
 4 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index 37f5b0385f..d37fafdc84 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 import select
 import sys
 from collections import defaultdict
@@ -12,6 +13,10 @@
 
 assert not TYPE_CHECKING or sys.platform == "linux"
 
+if TYPE_CHECKING:
+    from typing_extensions import TypeAlias
+EventResult: TypeAlias = "list[tuple[int, int]]"
+
 
 @attr.s(slots=True, eq=False, frozen=True)
 class _EpollStatistics:
@@ -222,14 +227,14 @@ def force_wakeup(self):
     # Return value must be False-y IFF the timeout expired, NOT if any I/O
     # happened or force_wakeup was called. Otherwise it can be anything; gets
     # passed straight through to process_events.
-    def get_events(self, timeout) -> list[tuple[int, int]]:
+    def get_events(self, timeout) -> EventResult:
         # max_events must be > 0 or epoll gets cranky
         # accessing self._registered from a thread looks dangerous, but it's
         # OK because it doesn't matter if our value is a little bit off.
         max_events = max(1, len(self._registered))
         return self._epoll.poll(timeout, max_events)
 
-    def process_events(self, events):
+    def process_events(self, events: EventResult):
         for fd, flags in events:
             if fd == self._force_wakeup_fd:
                 self._force_wakeup.drain()
diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index bdfa91efa5..8e3267c1a8 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 import errno
 import select
 import sys
@@ -13,6 +14,10 @@
 
 assert not TYPE_CHECKING or (sys.platform != "linux" and sys.platform != "win32")
 
+if TYPE_CHECKING:
+    from typing_extensions import TypeAlias
+EventResult: TypeAlias = "list[select.kevent]"
+
 
 @attr.s(slots=True, eq=False, frozen=True)
 class _KqueueStatistics:
@@ -53,7 +58,7 @@ def close(self):
     def force_wakeup(self):
         self._force_wakeup.wakeup_thread_and_signal_safe()
 
-    def get_events(self, timeout: float) -> list[select.kevent]:
+    def get_events(self, timeout: float) -> EventResult:
         # max_events must be > 0 or kqueue gets cranky
         # and we generally want this to be strictly larger than the actual
         # number of events we get, so that we can tell that we've gotten
@@ -70,7 +75,7 @@ def get_events(self, timeout: float) -> list[select.kevent]:
                 # and loop back to the start
         return events
 
-    def process_events(self, events):
+    def process_events(self, events: EventResult):
         for event in events:
             key = (event.ident, event.filter)
             if event.ident == self._force_wakeup_fd:
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 4084f72b6e..604804acd7 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 import enum
 import itertools
 import socket
@@ -29,6 +30,10 @@
 
 assert not TYPE_CHECKING or sys.platform == "win32"
 
+if TYPE_CHECKING:
+    from typing_extensions import TypeAlias
+EventResult: TypeAlias = int
+
 # There's a lot to be said about the overall design of a Windows event
 # loop. See
 #
@@ -485,7 +490,7 @@ def force_wakeup(self):
             )
         )
 
-    def get_events(self, timeout):
+    def get_events(self, timeout) -> EventResult:
         received = ffi.new("PULONG")
         milliseconds = round(1000 * timeout)
         if timeout > 0 and milliseconds == 0:
@@ -502,7 +507,7 @@ def get_events(self, timeout):
             return 0
         return received[0]
 
-    def process_events(self, received):
+    def process_events(self, received: EventResult):
         for i in range(received):
             entry = self._events[i]
             if entry.lpCompletionKey == CKeys.AFD_POLL:
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 66563ebe16..07a950f2b8 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -51,16 +51,12 @@
 
 if TYPE_CHECKING:
     import contextvars
-    import select
 
     # An unfortunate name collision here with trio._util.Final
     from typing import Final as FinalT
 
     from typing_extensions import Literal, Self, TypeAlias
 
-    # Result of IOManager.get_events() - Windows, Epoll, KQueue
-    EventResult: TypeAlias = 'int | list[tuple[int, int]] | list[select.kevent]'
-
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 
 # Passed as a sentinel
@@ -2688,13 +2684,13 @@ async def checkpoint_if_cancelled() -> None:
 
 if sys.platform == "win32":
     from ._generated_io_windows import *
-    from ._io_windows import WindowsIOManager as TheIOManager
+    from ._io_windows import WindowsIOManager as TheIOManager, EventResult as EventResult
 elif sys.platform == "linux" or (not TYPE_CHECKING and hasattr(select, "epoll")):
     from ._generated_io_epoll import *
-    from ._io_epoll import EpollIOManager as TheIOManager
+    from ._io_epoll import EpollIOManager as TheIOManager, EventResult as EventResult
 elif TYPE_CHECKING or hasattr(select, "kqueue"):
     from ._generated_io_kqueue import *
-    from ._io_kqueue import KqueueIOManager as TheIOManager
+    from ._io_kqueue import KqueueIOManager as TheIOManager, EventResult as EventResult
 else:  # pragma: no cover
     raise NotImplementedError("unsupported platform")
 

From 56b64b6c28d2aa8edf3095ca3519c5fdc6602a6f Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:11:15 +1000
Subject: [PATCH 19/56] Add types to everything in _run

---
 pyproject.toml            |   1 +
 trio/_core/_io_windows.py |   4 +-
 trio/_core/_run.py        | 123 ++++++++++++++++++++++----------------
 3 files changed, 76 insertions(+), 52 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 90bc98f64f..8b48af7dec 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -74,6 +74,7 @@ disallow_subclassing_any = true
 # Needs to use Any due to some complex introspection.
 module = [
     "trio._path",
+    "trio._core._run",
 ]
 disallow_incomplete_defs = true
 disallow_untyped_defs = true
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 604804acd7..35ca459d6e 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -505,7 +505,9 @@ def get_events(self, timeout) -> EventResult:
             if exc.winerror != ErrorCodes.WAIT_TIMEOUT:  # pragma: no cover
                 raise
             return 0
-        return received[0]
+        result = received[0]
+        assert isinstance(result, int)
+        return result
 
     def process_events(self, received: EventResult):
         for i in range(received):
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 07a950f2b8..8c4d1e1def 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -10,7 +10,7 @@
 import threading
 import warnings
 from collections import deque
-from collections.abc import Coroutine, Generator, Iterator
+from collections.abc import Callable, Coroutine, Generator, Iterator, Sequence
 from contextlib import AbstractAsyncContextManager, contextmanager
 from contextvars import copy_context
 from heapq import heapify, heappop, heappush
@@ -27,6 +27,7 @@
 import trio._core._ki
 
 from .. import _core
+from .._abc import Clock, Instrument
 from .._util import Final, NoPublicConstructor, coroutine_or_error
 from ._asyncgens import AsyncGenerators
 from ._entry_queue import EntryQueue, TrioToken
@@ -60,12 +61,13 @@
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 
 # Passed as a sentinel
-_NO_SEND: Outcome[Any] = cast(Outcome, object())
+_NO_SEND: FinalT = cast("Outcome[Any]", object())
 
 FnT = TypeVar("FnT", bound="Callable[..., Any]")
 StatusT = TypeVar("StatusT")
 StatusT_co = TypeVar("StatusT_co", covariant=True)
 StatusT_contra = TypeVar("StatusT_contra", contravariant=True)
+RetT = TypeVar("RetT")
 
 
 # Decorator to mark methods public. This does nothing by itself, but
@@ -128,7 +130,7 @@ def function_with_unique_name_xyzzy() -> NoReturn:
 
 
 @attr.s(frozen=True, slots=True)
-class SystemClock:
+class SystemClock(Clock):
     # Add a large random offset to our clock to ensure that if people
     # accidentally call time.perf_counter() directly or start comparing clocks
     # between different runs, then they'll notice the bug quickly:
@@ -1067,7 +1069,9 @@ def _child_finished(self, task: Task, outcome: Outcome[Any]) -> None:
             self._add_exc(outcome.error)
         self._check_nursery_closed()
 
-    async def _nested_child_finished(self, nested_child_exc: BaseException) -> BaseException | None:
+    async def _nested_child_finished(
+        self, nested_child_exc: BaseException | None
+    ) -> BaseException | None:
         # Returns MultiError instance (or any exception if the nursery is in loose mode
         # and there is just one contained exception) if there are pending exceptions
         if nested_child_exc is not None:
@@ -1080,7 +1084,8 @@ async def _nested_child_finished(self, nested_child_exc: BaseException) -> BaseE
             # KeyboardInterrupt), then save that, but still wait until our
             # children finish.
             def aborted(raise_cancel: Callable[[], NoReturn]) -> Abort:
-                self._add_exc(capture(raise_cancel).error)
+                # capture() needs an overload for NoReturn -> Error.
+                self._add_exc(capture(raise_cancel).error)  # type: ignore[union-attr]
                 return Abort.FAILED
 
             self._parent_waiting_in_aexit = True
@@ -1105,6 +1110,7 @@ def aborted(raise_cancel: Callable[[], NoReturn]) -> Abort:
                 # avoid a garbage cycle
                 # (see test_nursery_cancel_doesnt_create_cyclic_garbage)
                 del self._pending_excs
+        return None
 
     def start_soon(
         self,
@@ -1420,13 +1426,14 @@ class RunContext(threading.local):
 
 GLOBAL_RUN_CONTEXT: FinalT = RunContext()
 
+
 class _IOStats(Protocol):
     @property
     def backend(self) -> str:
         ...
 
 
-@attr.s(frozen=True)
+@attr.frozen
 class RunStatistics:
     """An object containing run-loop-level debugging information.
 
@@ -1449,11 +1456,13 @@ class RunStatistics:
       naming which operating-system-specific I/O backend is in use; the
       other attributes vary between backends.
     """
-    tasks_living: int = attr.ib()
-    tasks_runnable: int = attr.ib()
-    seconds_to_next_deadline: float = attr.ib()
-    io_statistics: _IOStats = attr.ib()
-    run_sync_soon_queue_size: int = attr.ib()
+
+    tasks_living: int
+    tasks_runnable: int
+    seconds_to_next_deadline: float
+    io_statistics: _IOStats
+    run_sync_soon_queue_size: int
+
 
 # This holds all the state that gets trampolined back and forth between
 # callbacks when we're running in guest mode.
@@ -1480,14 +1489,15 @@ class GuestState:
     run_sync_soon_threadsafe: Callable[[Callable[[], object]], object] = attr.ib()
     run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object] = attr.ib()
     done_callback: Callable[[Outcome[Any]], object] = attr.ib()
-    unrolled_run_gen: Generator[None, None, None] = attr.ib()
+    unrolled_run_gen: Generator[float, EventResult, None] = attr.ib()
     _value_factory: Callable[[], Value[Any]] = lambda: Value(None)
-    unrolled_run_next_send: Outcome[Any] = attr.ib(factory=_value_factory, type=Outcome)
+    unrolled_run_next_send: Outcome[Any] = attr.ib(factory=_value_factory)
 
     def guest_tick(self) -> None:
         try:
             timeout = self.unrolled_run_next_send.send(self.unrolled_run_gen)
         except StopIteration:
+            assert self.runner.main_task_outcome is not None
             self.done_callback(self.runner.main_task_outcome)
             return
         except TrioInternalError as exc:
@@ -1495,7 +1505,9 @@ def guest_tick(self) -> None:
             return
 
         # Optimization: try to skip going into the thread if we can avoid it
-        events_outcome: Value[EventResult] | Error = capture(self.runner.io_manager.get_events, 0)
+        events_outcome: Value[EventResult] | Error = capture(
+            self.runner.io_manager.get_events, 0
+        )
         if timeout <= 0 or isinstance(events_outcome, Error) or events_outcome.value:
             # No need to go into the thread
             self.unrolled_run_next_send = events_outcome
@@ -1537,7 +1549,7 @@ class Runner:
 
     init_task: Task | None = attr.ib(default=None)
     system_nursery: Nursery | None = attr.ib(default=None)
-    system_context = attr.ib(default=None)
+    system_context: contextvars.Context = attr.ib(kw_only=True)
     main_task: Task | None = attr.ib(default=None)
     main_task_outcome: Outcome[Any] | None = attr.ib(default=None)
 
@@ -1633,7 +1645,7 @@ def current_root_task(self) -> trio.lowlevel.Task | None:
 
     @_public
     def reschedule(
-        self, task: trio.lowlevel.Task, next_send: Outcome = _NO_SEND
+        self, task: trio.lowlevel.Task, next_send: Outcome[Any] = _NO_SEND
     ) -> None:
         """Reschedule the given task with the given
         :class:`outcome.Outcome`.
@@ -1721,7 +1733,7 @@ def spawn_impl(
 
         if not hasattr(coro, "cr_frame"):
             # This async function is implemented in C or Cython
-            async def python_wrapper(orig_coro):
+            async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
                 return await orig_coro
 
             coro = python_wrapper(coro)
@@ -1746,7 +1758,7 @@ async def python_wrapper(orig_coro):
         self.reschedule(task, None)  # type: ignore[arg-type]
         return task
 
-    def task_exited(self, task: Task, outcome: Outcome) -> None:
+    def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
         if (
             task._cancel_status is not None
             and task._cancel_status.abandoned_by_misnesting
@@ -1920,7 +1932,7 @@ def current_trio_token(self) -> trio.lowlevel.TrioToken:
     # KI handling
     ################
 
-    ki_pending = attr.ib(default=False)
+    ki_pending: bool = attr.ib(default=False)
 
     # deliver_ki is broke. Maybe move all the actual logic and state into
     # RunToken, and we'll only have one instance per runner? But then we can't
@@ -1929,14 +1941,14 @@ def current_trio_token(self) -> trio.lowlevel.TrioToken:
     # keep the class public so people can isinstance() it if they want.
 
     # This gets called from signal context
-    def deliver_ki(self):
+    def deliver_ki(self) -> None:
         self.ki_pending = True
         try:
             self.entry_queue.run_sync_soon(self._deliver_ki_cb)
         except RunFinishedError:
             pass
 
-    def _deliver_ki_cb(self):
+    def _deliver_ki_cb(self) -> None:
         if not self.ki_pending:
             return
         # Can't happen because main_task and run_sync_soon_task are created at
@@ -1953,7 +1965,9 @@ def _deliver_ki_cb(self):
     # Quiescing
     ################
 
-    waiting_for_idle = attr.ib(factory=SortedDict)
+    # sortedcontainers doesn't have types, and is reportedly very hard to type:
+    # https://github.com/grantjenks/python-sortedcontainers/issues/68
+    waiting_for_idle: Any = attr.ib(factory=SortedDict)
 
     @_public
     async def wait_all_tasks_blocked(self, cushion: float = 0.0) -> None:
@@ -2018,7 +2032,7 @@ async def test_lock_fairness():
         key = (cushion, id(task))
         self.waiting_for_idle[key] = task
 
-        def abort(_):
+        def abort(_: Callable[[], Any]) -> Abort:
             del self.waiting_for_idle[key]
             return Abort.SUCCEEDED
 
@@ -2093,11 +2107,11 @@ def abort(_):
 
 
 def setup_runner(
-    clock,
-    instruments,
-    restrict_keyboard_interrupt_to_checkpoints,
-    strict_exception_groups,
-):
+    clock: Clock | None,
+    instruments: Sequence[Instrument],
+    restrict_keyboard_interrupt_to_checkpoints: bool,
+    strict_exception_groups: bool,
+) -> Runner:
     """Create a Runner object and install it as the GLOBAL_RUN_CONTEXT."""
     # It wouldn't be *hard* to support nested calls to run(), but I can't
     # think of a single good reason for it, so let's be conservative for
@@ -2107,14 +2121,14 @@ def setup_runner(
 
     if clock is None:
         clock = SystemClock()
-    instruments = Instruments(instruments)
+    instrument_group = Instruments(instruments)
     io_manager = TheIOManager()
     system_context = copy_context()
     ki_manager = KIManager()
 
     runner = Runner(
         clock=clock,
-        instruments=instruments,
+        instruments=instrument_group,
         io_manager=io_manager,
         system_context=system_context,
         ki_manager=ki_manager,
@@ -2131,13 +2145,13 @@ def setup_runner(
 
 
 def run(
-    async_fn,
-    *args,
-    clock=None,
-    instruments=(),
+    async_fn: Callable[..., RetT],
+    *args: Any,
+    clock: Clock | None = None,
+    instruments: Sequence[Instrument] = (),
     restrict_keyboard_interrupt_to_checkpoints: bool = False,
     strict_exception_groups: bool = False,
-):
+) -> RetT:
     """Run a Trio-flavored async function, and return the result.
 
     Calling::
@@ -2220,7 +2234,8 @@ def run(
     )
 
     gen = unrolled_run(runner, async_fn, args)
-    next_send = None
+    # Need to send None in the first time.
+    next_send: EventResult = None  # type: ignore[assignment]
     while True:
         try:
             timeout = gen.send(next_send)
@@ -2230,23 +2245,26 @@ def run(
     # Inlined copy of runner.main_task_outcome.unwrap() to avoid
     # cluttering every single Trio traceback with an extra frame.
     if isinstance(runner.main_task_outcome, Value):
-        return runner.main_task_outcome.value
-    else:
+        return cast(RetT, runner.main_task_outcome.value)
+    elif isinstance(runner.main_task_outcome, Error):
         raise runner.main_task_outcome.error
+    else:  # pragma: no cover
+        raise AssertionError(runner.main_task_outcome)
 
 
 def start_guest_run(
-    async_fn,
-    *args,
-    run_sync_soon_threadsafe,
-    done_callback,
-    run_sync_soon_not_threadsafe=None,
+    async_fn: Callable[..., RetT],
+    *args: Any,
+    run_sync_soon_threadsafe: Callable[[Callable[[], object]], object],
+    done_callback: Callable[[Outcome[RetT]], object],
+    run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object]
+    | None = None,
     host_uses_signal_set_wakeup_fd: bool = False,
-    clock=None,
-    instruments=(),
+    clock: Clock | None = None,
+    instruments: Sequence[Instrument] = (),
     restrict_keyboard_interrupt_to_checkpoints: bool = False,
     strict_exception_groups: bool = False,
-):
+) -> None:
     """Start a "guest" run of Trio on top of some other "host" event loop.
 
     Each host loop can only have one guest run at a time.
@@ -2334,10 +2352,10 @@ def my_done_callback(run_outcome):
 # straight through.
 def unrolled_run(
     runner: Runner,
-    async_fn,
-    args,
+    async_fn: Callable[..., object],
+    args: Any,
     host_uses_signal_set_wakeup_fd: bool = False,
-) -> None:
+) -> Generator[float, EventResult, None]:
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
     __tracebackhide__ = True
 
@@ -2468,7 +2486,7 @@ def unrolled_run(
                 next_send_fn = task._next_send_fn
                 next_send = task._next_send
                 task._next_send_fn = task._next_send = None
-                final_outcome: Outcome | None = None
+                final_outcome: Outcome[Any] | None = None
                 try:
                     # We used to unwrap the Outcome object here and send/throw
                     # its contents in directly, but it turns out that .throw()
@@ -2684,7 +2702,10 @@ async def checkpoint_if_cancelled() -> None:
 
 if sys.platform == "win32":
     from ._generated_io_windows import *
-    from ._io_windows import WindowsIOManager as TheIOManager, EventResult as EventResult
+    from ._io_windows import (
+        WindowsIOManager as TheIOManager,
+        EventResult as EventResult,
+    )
 elif sys.platform == "linux" or (not TYPE_CHECKING and hasattr(select, "epoll")):
     from ._generated_io_epoll import *
     from ._io_epoll import EpollIOManager as TheIOManager, EventResult as EventResult

From b47c539a91a0e8e95f7e645de92aa3081c22666e Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:12:49 +1000
Subject: [PATCH 20/56] Enable strict Mypy settings for _generated_run

---
 pyproject.toml               | 1 +
 trio/_core/_generated_run.py | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 8b48af7dec..09453356c3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,6 +51,7 @@ disallow_untyped_defs = false
 module = [
     "trio._abc",
     "trio._core._entry_queue",
+    "trio._core._generated_run",
     "trio._core._local",
     "trio._core._unbounded_queue",
     "trio._deprecate",
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index 4d843bf0f6..fe1b8f0fab 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -85,7 +85,8 @@ def current_root_task() ->(trio.lowlevel.Task | None):
         raise RuntimeError("must be called from async context")
 
 
-def reschedule(task: trio.lowlevel.Task, next_send: Outcome=_NO_SEND) ->None:
+def reschedule(task: trio.lowlevel.Task, next_send: Outcome[Any]=_NO_SEND
+    ) ->None:
     """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 

From c2c587aa1f22212db5e7c58ce793e086c3bab988 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:20:51 +1000
Subject: [PATCH 21/56] Use a sentinel for TaskStatus._value

---
 trio/_core/_run.py | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 8c4d1e1def..ede29e3ac7 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -17,7 +17,7 @@
 from math import inf
 from time import perf_counter
 from types import TracebackType
-from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, cast, overload
+from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, cast, overload, final
 
 import attr
 from outcome import Error, Outcome, Value, capture
@@ -70,6 +70,11 @@
 RetT = TypeVar("RetT")
 
 
+@final
+class _NoStatus(metaclass=NoPublicConstructor):
+    """Sentinel for unset TaskStatus._value."""
+
+
 # Decorator to mark methods public. This does nothing by itself, but
 # trio/_tools/gen_exports.py looks for it.
 def _public(fn: FnT) -> FnT:
@@ -825,27 +830,24 @@ def started(self, value: StatusT_contra | None = None) -> None:
 class _TaskStatus(TaskStatus[StatusT]):
     _old_nursery: Nursery = attr.ib()
     _new_nursery: Nursery = attr.ib()
-    # _called_started indicates whether _value can be accessed. The latter can't be Optional[],
-    # since None is a valid value too.
-    _called_started: bool = attr.ib(default=False)
-    _value: StatusT = attr.ib(default=cast(StatusT, None))
+    # NoStatus is a sentinel.
+    _value: StatusT | type[_NoStatus] = attr.ib(default=_NoStatus)
 
     def __repr__(self) -> str:
         return f"<Task status object at {id(self):#x}>"
 
     @overload
-    def started(self: TaskStatus[None]) -> None:
+    def started(self: _TaskStatus[None]) -> None:
         ...
 
     @overload
-    def started(self, value: StatusT) -> None:
+    def started(self: _TaskStatus[StatusT], value: StatusT) -> None:
         ...
 
-    def started(self, value: StatusT = None) -> None:  # type: ignore[assignment] # StatusT != None
-        if self._called_started:
+    def started(self, value: StatusT | None = None) -> None:
+        if self._value is not _NoStatus:
             raise RuntimeError("called 'started' twice on the same task status")
-        self._called_started = True
-        self._value = value
+        self._value = cast(StatusT, value)  # If None, StatusT == None
 
         # If the old nursery is cancelled, then quietly quit now; the child
         # will eventually exit on its own, and we don't want to risk moving
@@ -1218,9 +1220,9 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
             # If we get here, then the child either got reparented or exited
             # normally. The complicated logic is all in TaskStatus.started().
             # (Any exceptions propagate directly out of the above.)
-            if not task_status._called_started:
+            if task_status._value is _NoStatus:
                 raise RuntimeError("child exited without calling task_status.started()")
-            return task_status._value
+            return task_status._value  # type: ignore[return-value]  # Mypy doesn't narrow yet.
         finally:
             self._pending_starts -= 1
             self._check_nursery_closed()

From 91bf0e4c3af226450d68c314e580717fade9ef10 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:23:58 +1000
Subject: [PATCH 22/56] Run linters

---
 trio/_core/_io_epoll.py       |  1 +
 trio/_core/_io_kqueue.py      |  1 +
 trio/_core/_io_windows.py     |  1 +
 trio/_core/_run.py            | 15 ++++++++++++---
 trio/_tests/verify_types.json | 11 ++++-------
 5 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index d37fafdc84..48ee09e3f6 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import select
 import sys
 from collections import defaultdict
diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index 8e3267c1a8..b58b57b7c4 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import errno
 import select
 import sys
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 35ca459d6e..75784c5ed2 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import enum
 import itertools
 import socket
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index ede29e3ac7..ad0afa4b69 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -17,7 +17,16 @@
 from math import inf
 from time import perf_counter
 from types import TracebackType
-from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, cast, overload, final
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    NoReturn,
+    Protocol,
+    TypeVar,
+    cast,
+    final,
+    overload,
+)
 
 import attr
 from outcome import Error, Outcome, Value, capture
@@ -2705,15 +2714,15 @@ async def checkpoint_if_cancelled() -> None:
 if sys.platform == "win32":
     from ._generated_io_windows import *
     from ._io_windows import (
-        WindowsIOManager as TheIOManager,
         EventResult as EventResult,
+        WindowsIOManager as TheIOManager,
     )
 elif sys.platform == "linux" or (not TYPE_CHECKING and hasattr(select, "epoll")):
     from ._generated_io_epoll import *
     from ._io_epoll import EpollIOManager as TheIOManager, EventResult as EventResult
 elif TYPE_CHECKING or hasattr(select, "kqueue"):
     from ._generated_io_kqueue import *
-    from ._io_kqueue import KqueueIOManager as TheIOManager, EventResult as EventResult
+    from ._io_kqueue import EventResult as EventResult, KqueueIOManager as TheIOManager
 else:  # pragma: no cover
     raise NotImplementedError("unsupported platform")
 
diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index 2160ae19ad..9c053af9cc 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -7,11 +7,11 @@
     "warningCount": 0
   },
   "typeCompleteness": {
-    "completenessScore": 0.9330143540669856,
+    "completenessScore": 0.9378980891719745,
     "exportedSymbolCounts": {
       "withAmbiguousType": 0,
-      "withKnownType": 585,
-      "withUnknownType": 42
+      "withKnownType": 589,
+      "withUnknownType": 39
     },
     "ignoreUnknownTypesFromImports": true,
     "missingClassDocStringCount": 1,
@@ -46,7 +46,7 @@
     ],
     "otherSymbolCounts": {
       "withAmbiguousType": 3,
-      "withKnownType": 601,
+      "withKnownType": 615,
       "withUnknownType": 52
     },
     "packageName": "trio",
@@ -84,12 +84,10 @@
       "trio.from_thread.run",
       "trio.from_thread.run_sync",
       "trio.lowlevel.cancel_shielded_checkpoint",
-      "trio.lowlevel.current_statistics",
       "trio.lowlevel.notify_closing",
       "trio.lowlevel.open_process",
       "trio.lowlevel.permanently_detach_coroutine_object",
       "trio.lowlevel.reattach_detached_coroutine_object",
-      "trio.lowlevel.start_guest_run",
       "trio.lowlevel.start_thread_soon",
       "trio.lowlevel.temporarily_detach_coroutine_object",
       "trio.lowlevel.wait_readable",
@@ -98,7 +96,6 @@
       "trio.open_ssl_over_tcp_stream",
       "trio.open_tcp_listeners",
       "trio.open_unix_socket",
-      "trio.run",
       "trio.run_process",
       "trio.serve_listeners",
       "trio.serve_ssl_over_tcp",

From f9cd90d0e1a9371e86a964bcd7f4693ad750b474 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:47:07 +1000
Subject: [PATCH 23/56] Add enough types to make _io_kqueue pass, for now

---
 trio/_core/_io_kqueue.py | 3 ++-
 trio/_core/_run.py       | 3 +++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index b58b57b7c4..bf0f9666c3 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -3,6 +3,7 @@
 import errno
 import select
 import sys
+from collections.abc import Callable, Generator
 from contextlib import contextmanager
 from typing import TYPE_CHECKING
 
@@ -31,7 +32,7 @@ class _KqueueStatistics:
 class KqueueIOManager:
     _kqueue = attr.ib(factory=select.kqueue)
     # {(ident, filter): Task or UnboundedQueue}
-    _registered = attr.ib(factory=dict)
+    _registered: dict[tuple[int, int], _core.Task | _core.UnboundedQueue] = attr.ib(factory=dict)
     _force_wakeup = attr.ib(factory=WakeupSocketpair)
     _force_wakeup_fd = attr.ib(default=None)
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index ad0afa4b69..fe23a5d864 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -467,6 +467,7 @@ def effective_deadline(self) -> float:
 """
 
 
+@final
 @attr.s(eq=False, repr=False, slots=True)
 class CancelScope(metaclass=Final):
     """A *cancellation scope*: the link between a unit of cancellable
@@ -1004,6 +1005,7 @@ def open_nursery(
     return NurseryManager(strict_exception_groups=strict_exception_groups)
 
 
+@final
 class Nursery(metaclass=NoPublicConstructor):
     """A context which may be used to spawn (or cancel) child tasks.
 
@@ -1245,6 +1247,7 @@ def __del__(self) -> None:
 ################################################################
 
 
+@final
 @attr.s(eq=False, hash=False, repr=False, slots=True)
 class Task(metaclass=NoPublicConstructor):
     _parent_nursery: Nursery | None = attr.ib()

From d4380a8417772f9bd13103ec10ada5022fe2501e Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 11:47:22 +1000
Subject: [PATCH 24/56] Make this an assertion, should not happen.

---
 trio/_core/_run.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index fe23a5d864..1239aee0a0 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -2519,8 +2519,7 @@ def unrolled_run(
                     # more Context.run adds.
                     tb = task_exc.__traceback__
                     for _ in range(1 + CONTEXT_RUN_TB_FRAMES):
-                        if tb is None:
-                            break
+                        assert tb is not None
                         tb = tb.tb_next
                     final_outcome = Error(task_exc.with_traceback(tb))
                     # Remove local refs so that e.g. cancelled coroutine locals

From 9cce973a24b6797eea4f6681d67b158766968936 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 12:19:27 +1000
Subject: [PATCH 25/56] Hide NurseryStartFunc, it doesn't need to be exposed

---
 docs/source/conf.py            |  2 ++
 docs/source/reference-core.rst | 12 +++++++-----
 trio/_core/_run.py             |  6 +++---
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 142dc7ee2c..cab42e77c8 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -63,7 +63,9 @@
     ("py:obj", "trio._abc.T"),
     ("py:obj", "trio._abc.T_resource"),
     ("py:class", "trio._core._run.StatusT"),
+    ("py:class", "trio._core._run.StatusT_co"),
     ("py:class", "trio._core._run.StatusT_contra"),
+    ("py:class", "trio._core._run.RetT"),
     ("py:class", "trio._threads.T"),
     # why aren't these found in stdlib?
     ("py:class", "types.FrameType"),
diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index d3fd970b5f..9e010dd349 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -916,19 +916,21 @@ The nursery API
 
 
 .. autoclass:: Nursery()
-   :members:
+   :members: child_tasks, parent_task
 
-.. autoclass:: TaskStatus(Protocol[StatusT])
-   :members:
+   .. automethod:: start(async_fn, *args, name = None)
+
+   .. automethod:: start_soon(async_fn, *args, name = None)
 
 .. attribute:: TASK_STATUS_IGNORED
    :type: TaskStatus
 
-   See :meth:`~Nursery.start`.
+   See :meth:`Nursery.start`.
 
-.. autoclass:: TaskStatus
+.. autoclass:: TaskStatus(Protocol[StatusT])
    :members:
 
+
 .. _task-local-storage:
 
 Task-local storage
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 1239aee0a0..fa3e54211d 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -907,8 +907,8 @@ def started(self, value: StatusT | None = None) -> None:
         self._old_nursery._check_nursery_closed()
 
 
-class NurseryStartFunc(Protocol[StatusT_co]):
-    """Type of functions passed to Nursery.start()."""
+class _NurseryStartFunc(Protocol[StatusT_co]):
+    """Type of functions passed to `nursery.start() <trio.Nursery.start>`."""
 
     def __call__(
         self, *args: Any, task_status: TaskStatus[StatusT_co]
@@ -1174,7 +1174,7 @@ def start_soon(
         GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)
 
     async def start(
-        self, async_fn: NurseryStartFunc[StatusT], *args: Any, name: object = None
+        self, async_fn: _NurseryStartFunc[StatusT], *args: Any, name: object = None
     ) -> StatusT:
         r"""Creates and initializes a child task.
 

From fc1ebdfa223fcf01a775872edca8ae86245fd27f Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 12:58:50 +1000
Subject: [PATCH 26/56] Remove unused imports

---
 trio/_core/_io_kqueue.py | 5 +++--
 trio/_core/_run.py       | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index bf0f9666c3..e9fbbe1c2d 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -3,7 +3,6 @@
 import errno
 import select
 import sys
-from collections.abc import Callable, Generator
 from contextlib import contextmanager
 from typing import TYPE_CHECKING
 
@@ -32,7 +31,9 @@ class _KqueueStatistics:
 class KqueueIOManager:
     _kqueue = attr.ib(factory=select.kqueue)
     # {(ident, filter): Task or UnboundedQueue}
-    _registered: dict[tuple[int, int], _core.Task | _core.UnboundedQueue] = attr.ib(factory=dict)
+    _registered: dict[tuple[int, int], _core.Task | _core.UnboundedQueue] = attr.ib(
+        factory=dict
+    )
     _force_wakeup = attr.ib(factory=WakeupSocketpair)
     _force_wakeup_fd = attr.ib(default=None)
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index fa3e54211d..a183aed903 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -65,7 +65,7 @@
     # An unfortunate name collision here with trio._util.Final
     from typing import Final as FinalT
 
-    from typing_extensions import Literal, Self, TypeAlias
+    from typing_extensions import Self
 
 DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: FinalT = 1000
 

From b67d344ac2d358895077afb2b8271c2781fe1300 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 12:59:33 +1000
Subject: [PATCH 27/56] Ignore __final__ attribute added by typing.final in
 3.11+

---
 trio/_tests/test_exports.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/trio/_tests/test_exports.py b/trio/_tests/test_exports.py
index 2b7a3947f2..c0da975fb4 100644
--- a/trio/_tests/test_exports.py
+++ b/trio/_tests/test_exports.py
@@ -318,13 +318,14 @@ def lookup_symbol(symbol):
             "__annotations__",
             "__attrs_attrs__",
             "__attrs_own_setattr__",
+            "__callable_proto_members_only__",
             "__class_getitem__",
+            "__final__",
             "__getstate__",
             "__match_args__",
             "__order__",
             "__orig_bases__",
             "__parameters__",
-            "__callable_proto_members_only__",
             "__protocol_attrs__",
             "__setstate__",
             "__slots__",

From 7d577a31d4ef94492583cdd409b26af1a4ec3a48 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 4 Aug 2023 13:24:42 +1000
Subject: [PATCH 28/56] Tweak some types to bring in line with Jakkdl's stubs

---
 trio/_core/_run.py | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index a183aed903..409dfb9de9 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -10,7 +10,14 @@
 import threading
 import warnings
 from collections import deque
-from collections.abc import Callable, Coroutine, Generator, Iterator, Sequence
+from collections.abc import (
+    Awaitable,
+    Callable,
+    Coroutine,
+    Generator,
+    Iterator,
+    Sequence,
+)
 from contextlib import AbstractAsyncContextManager, contextmanager
 from contextvars import copy_context
 from heapq import heapify, heappop, heappush
@@ -1096,7 +1103,7 @@ async def _nested_child_finished(
             # If we get cancelled (or have an exception injected, like
             # KeyboardInterrupt), then save that, but still wait until our
             # children finish.
-            def aborted(raise_cancel: Callable[[], NoReturn]) -> Abort:
+            def aborted(raise_cancel: _core.RaiseCancelT) -> Abort:
                 # capture() needs an overload for NoReturn -> Error.
                 self._add_exc(capture(raise_cancel).error)  # type: ignore[union-attr]
                 return Abort.FAILED
@@ -1270,7 +1277,7 @@ class Task(metaclass=NoPublicConstructor):
     # Tasks start out unscheduled.
     _next_send_fn: Callable[[Any], object] = attr.ib(default=None)
     _next_send: Outcome[Any] | None | BaseException = attr.ib(default=None)
-    _abort_func: Callable[[Callable[[], NoReturn]], Abort] | None = attr.ib(
+    _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = attr.ib(
         default=None
     )
     custom_sleep_data: Any = attr.ib(default=None)
@@ -1385,7 +1392,7 @@ def _activate_cancel_status(self, cancel_status: CancelStatus | None) -> None:
             if self._cancel_status.effectively_cancelled:
                 self._attempt_delivery_of_any_pending_cancel()
 
-    def _attempt_abort(self, raise_cancel: Callable[[], NoReturn]) -> None:
+    def _attempt_abort(self, raise_cancel: _core.RaiseCancelT) -> None:
         # Either the abort succeeds, in which case we will reschedule the
         # task, or else it fails, in which case it will worry about
         # rescheduling itself (hopefully eventually calling reraise to raise
@@ -2046,7 +2053,7 @@ async def test_lock_fairness():
         key = (cushion, id(task))
         self.waiting_for_idle[key] = task
 
-        def abort(_: Callable[[], Any]) -> Abort:
+        def abort(_: _core.RaiseCancelT) -> Abort:
             del self.waiting_for_idle[key]
             return Abort.SUCCEEDED
 
@@ -2267,7 +2274,7 @@ def run(
 
 
 def start_guest_run(
-    async_fn: Callable[..., RetT],
+    async_fn: Callable[..., Awaitable[RetT]],
     *args: Any,
     run_sync_soon_threadsafe: Callable[[Callable[[], object]], object],
     done_callback: Callable[[Outcome[RetT]], object],
@@ -2367,7 +2374,7 @@ def my_done_callback(run_outcome):
 def unrolled_run(
     runner: Runner,
     async_fn: Callable[..., object],
-    args: Any,
+    args: tuple[Any, ...],
     host_uses_signal_set_wakeup_fd: bool = False,
 ) -> Generator[float, EventResult, None]:
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True

From 6b486cdf803e84e53f5db1925d8f9e77a9f94639 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sun, 6 Aug 2023 16:03:08 +1000
Subject: [PATCH 29/56] Revert change to assertions

---
 trio/_core/_run.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 409dfb9de9..e82728a168 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -2526,8 +2526,8 @@ def unrolled_run(
                     # more Context.run adds.
                     tb = task_exc.__traceback__
                     for _ in range(1 + CONTEXT_RUN_TB_FRAMES):
-                        assert tb is not None
-                        tb = tb.tb_next
+                        if tb is not None:  # pragma: no branch
+                            tb = tb.tb_next
                     final_outcome = Error(task_exc.with_traceback(tb))
                     # Remove local refs so that e.g. cancelled coroutine locals
                     # are not kept alive by this frame until another exception

From 0397765affd7dc15dfcdf2cf1963ff133dd919aa Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Mon, 7 Aug 2023 15:37:57 +1000
Subject: [PATCH 30/56] Move RunStatistics to trio.lowlevel alongside
 TaskStatus

---
 docs/source/reference-lowlevel.rst | 2 +-
 trio/__init__.py                   | 2 --
 trio/_core/_run.py                 | 4 +---
 trio/_tests/verify_types.json      | 8 +++-----
 trio/lowlevel.py                   | 1 +
 5 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst
index 2ca75e4ab6..301f8cedc3 100644
--- a/docs/source/reference-lowlevel.rst
+++ b/docs/source/reference-lowlevel.rst
@@ -53,7 +53,7 @@ Global statistics
 
    Returns an object containing run-loop-level debugging information:
 
-.. autoclass:: trio.RunStatistics()
+.. autoclass:: RunStatistics()
 
 
 The current clock
diff --git a/trio/__init__.py b/trio/__init__.py
index 97efa368de..277baa5339 100644
--- a/trio/__init__.py
+++ b/trio/__init__.py
@@ -35,8 +35,6 @@
     EndOfChannel as EndOfChannel,
     Nursery as Nursery,
     RunFinishedError as RunFinishedError,
-    RunStatistics as RunStatistics,
-    TaskStatus as TaskStatus,
     TrioInternalError as TrioInternalError,
     WouldBlock as WouldBlock,
     current_effective_deadline as current_effective_deadline,
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index e82728a168..a717b26b7b 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1277,9 +1277,7 @@ class Task(metaclass=NoPublicConstructor):
     # Tasks start out unscheduled.
     _next_send_fn: Callable[[Any], object] = attr.ib(default=None)
     _next_send: Outcome[Any] | None | BaseException = attr.ib(default=None)
-    _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = attr.ib(
-        default=None
-    )
+    _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = attr.ib(default=None)
     custom_sleep_data: Any = attr.ib(default=None)
 
     # For introspection and nursery.start()
diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index 9c053af9cc..be5756b68c 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -7,11 +7,11 @@
     "warningCount": 0
   },
   "typeCompleteness": {
-    "completenessScore": 0.9378980891719745,
+    "completenessScore": 0.9411764705882353,
     "exportedSymbolCounts": {
       "withAmbiguousType": 0,
-      "withKnownType": 589,
-      "withUnknownType": 39
+      "withKnownType": 592,
+      "withUnknownType": 37
     },
     "ignoreUnknownTypesFromImports": true,
     "missingClassDocStringCount": 1,
@@ -51,7 +51,6 @@
     },
     "packageName": "trio",
     "symbols": [
-      "trio._core._mock_clock.MockClock.jump",
       "trio._highlevel_socket.SocketStream.getsockopt",
       "trio._highlevel_socket.SocketStream.send_all",
       "trio._highlevel_socket.SocketStream.setsockopt",
@@ -94,7 +93,6 @@
       "trio.lowlevel.wait_writable",
       "trio.open_ssl_over_tcp_listeners",
       "trio.open_ssl_over_tcp_stream",
-      "trio.open_tcp_listeners",
       "trio.open_unix_socket",
       "trio.run_process",
       "trio.serve_listeners",
diff --git a/trio/lowlevel.py b/trio/lowlevel.py
index c66e22b60e..fc55f9eabe 100644
--- a/trio/lowlevel.py
+++ b/trio/lowlevel.py
@@ -13,6 +13,7 @@
     ParkingLot as ParkingLot,
     ParkingLotStatistics as ParkingLotStatistics,
     RaiseCancelT as RaiseCancelT,
+    RunStatistics as RunStatistics,
     RunVar as RunVar,
     Task as Task,
     TaskStatus as TaskStatus,

From e9e5577f8678a16583deac4398c66bcf5a6d3592 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Mon, 7 Aug 2023 16:38:02 +1000
Subject: [PATCH 31/56] Actually, it should be in the main namespace, since
 it's required for Nursery.start()

---
 docs/source/reference-lowlevel.rst    | 3 ---
 trio/__init__.py                      | 1 +
 trio/_highlevel_open_tcp_listeners.py | 2 +-
 trio/_tests/verify_types.json         | 8 ++++----
 trio/lowlevel.py                      | 1 -
 5 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst
index 301f8cedc3..712a36ad04 100644
--- a/docs/source/reference-lowlevel.rst
+++ b/docs/source/reference-lowlevel.rst
@@ -538,9 +538,6 @@ Task API
       putting a task to sleep and then waking it up again. (See
       :func:`wait_task_rescheduled` for details.)
 
-.. autoclass:: TaskStatus
-   :members:
-
 .. _guest-mode:
 
 Using "guest mode" to run Trio on top of other event loops
diff --git a/trio/__init__.py b/trio/__init__.py
index 277baa5339..be7de42cde 100644
--- a/trio/__init__.py
+++ b/trio/__init__.py
@@ -35,6 +35,7 @@
     EndOfChannel as EndOfChannel,
     Nursery as Nursery,
     RunFinishedError as RunFinishedError,
+    TaskStatus as TaskStatus,
     TrioInternalError as TrioInternalError,
     WouldBlock as WouldBlock,
     current_effective_deadline as current_effective_deadline,
diff --git a/trio/_highlevel_open_tcp_listeners.py b/trio/_highlevel_open_tcp_listeners.py
index 0d5f630495..93e82c8c53 100644
--- a/trio/_highlevel_open_tcp_listeners.py
+++ b/trio/_highlevel_open_tcp_listeners.py
@@ -6,7 +6,7 @@
 from math import inf
 
 import trio
-from trio.lowlevel import TaskStatus
+from trio import TaskStatus
 
 from . import socket as tsocket
 
diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index be5756b68c..4e51c52e1d 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -7,10 +7,10 @@
     "warningCount": 0
   },
   "typeCompleteness": {
-    "completenessScore": 0.9411764705882353,
+    "completenessScore": 0.9410828025477707,
     "exportedSymbolCounts": {
       "withAmbiguousType": 0,
-      "withKnownType": 592,
+      "withKnownType": 591,
       "withUnknownType": 37
     },
     "ignoreUnknownTypesFromImports": true,
@@ -46,8 +46,8 @@
     ],
     "otherSymbolCounts": {
       "withAmbiguousType": 3,
-      "withKnownType": 615,
-      "withUnknownType": 52
+      "withKnownType": 617,
+      "withUnknownType": 50
     },
     "packageName": "trio",
     "symbols": [
diff --git a/trio/lowlevel.py b/trio/lowlevel.py
index fc55f9eabe..25e64975e2 100644
--- a/trio/lowlevel.py
+++ b/trio/lowlevel.py
@@ -16,7 +16,6 @@
     RunStatistics as RunStatistics,
     RunVar as RunVar,
     Task as Task,
-    TaskStatus as TaskStatus,
     TrioToken as TrioToken,
     UnboundedQueue as UnboundedQueue,
     UnboundedQueueStatistics as UnboundedQueueStatistics,

From 617720a593cf818fb51ded4d5f1f76b1d97eefa0 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Tue, 8 Aug 2023 16:42:17 +1000
Subject: [PATCH 32/56] Fix incorrect references to these two objects

---
 trio/_core/_generated_run.py          | 2 +-
 trio/_core/_run.py                    | 2 +-
 trio/_dtls.py                         | 3 +--
 trio/_highlevel_open_tcp_listeners.py | 2 +-
 4 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index fe1b8f0fab..db9e516b44 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -16,7 +16,7 @@
 # fmt: off
 
 
-def current_statistics() ->trio.RunStatistics:
+def current_statistics() ->trio.lowlevel.RunStatistics:
     """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
         Currently, the following fields are defined:
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index a717b26b7b..bd384f7efe 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1599,7 +1599,7 @@ def close(self) -> None:
         self.ki_manager.close()
 
     @_public
-    def current_statistics(self) -> trio.RunStatistics:
+    def current_statistics(self) -> trio.lowlevel.RunStatistics:
         """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
         Currently, the following fields are defined:
diff --git a/trio/_dtls.py b/trio/_dtls.py
index 172d27ce99..08b7672a2f 100644
--- a/trio/_dtls.py
+++ b/trio/_dtls.py
@@ -42,7 +42,6 @@
     from OpenSSL.SSL import Context
     from typing_extensions import Self, TypeAlias
 
-    from trio.lowlevel import TaskStatus
     from trio.socket import Address, _SocketType
 
 MAX_UDP_PACKET_SIZE = 65527
@@ -1267,7 +1266,7 @@ async def serve(
         ssl_context: Context,
         async_fn: Callable[..., Awaitable[object]],
         *args: Any,
-        task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
+        task_status: trio.TaskStatus[None] = trio.TASK_STATUS_IGNORED,
     ) -> None:
         """Listen for incoming connections, and spawn a handler for each using an
         internal nursery.
diff --git a/trio/_highlevel_open_tcp_listeners.py b/trio/_highlevel_open_tcp_listeners.py
index 93e82c8c53..e6840eae97 100644
--- a/trio/_highlevel_open_tcp_listeners.py
+++ b/trio/_highlevel_open_tcp_listeners.py
@@ -171,7 +171,7 @@ async def serve_tcp(
     host: str | bytes | None = None,
     backlog: int | float | None = None,
     handler_nursery: trio.Nursery | None = None,
-    task_status: TaskStatus = trio.TASK_STATUS_IGNORED,  # type: ignore[assignment]  # default has type "_TaskStatusIgnored", argument has type "TaskStatus"
+    task_status: TaskStatus[list[trio.SocketListener]] = trio.TASK_STATUS_IGNORED,
 ) -> None:
     """Listen for incoming TCP connections, and for each one start a task
     running ``handler(stream)``.

From abe2e3ebcbba8769fea66ba3d8f8146ede9cd8c6 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Tue, 8 Aug 2023 16:46:59 +1000
Subject: [PATCH 33/56] Move `_core._run` out of mypy overrides for Any, just
 type-ignore.

---
 pyproject.toml     |  2 +-
 trio/_core/_run.py | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index cab2ee84aa..7bd3ba6018 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -54,6 +54,7 @@ module = [
     "trio._core._generated_run",
     "trio._core._local",
     "trio._core._unbounded_queue",
+    "trio._core._run",
     "trio._deprecate",
     "trio._dtls",
     "trio._file_io",
@@ -76,7 +77,6 @@ disallow_subclassing_any = true
 # Needs to use Any due to some complex introspection.
 module = [
     "trio._path",
-    "trio._core._run",
 ]
 disallow_incomplete_defs = true
 disallow_untyped_defs = true
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index bd384f7efe..3b3822a364 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1662,8 +1662,8 @@ def current_root_task(self) -> trio.lowlevel.Task | None:
     # Core task handling primitives
     ################
 
-    @_public
-    def reschedule(
+    @_public  # Type-ignore due to use of Any here.
+    def reschedule(  # type: ignore[misc]
         self, task: trio.lowlevel.Task, next_send: Outcome[Any] = _NO_SEND
     ) -> None:
         """Reschedule the given task with the given
@@ -1826,12 +1826,12 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
     # System tasks and init
     ################
 
-    @_public
-    def spawn_system_task(
+    @_public  # Type-ignore due to use of Any here.
+    def spawn_system_task(  # type: ignore[misc]
         self,
         # TODO: TypeVarTuple
         async_fn: Callable[..., Awaitable[object]],
-        *args: Any,
+        *args: object,
         name: object = None,
         context: contextvars.Context | None = None,
     ) -> trio.lowlevel.Task:

From 6245e0a7ad26e1c39da04568a2ad1307fdc0c029 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Tue, 8 Aug 2023 17:52:18 +1000
Subject: [PATCH 34/56] Update verify_types.json

---
 trio/_tests/verify_types.json | 27 +++++----------------------
 1 file changed, 5 insertions(+), 22 deletions(-)

diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index 5a0c59e33e..5f368b4cea 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -7,11 +7,11 @@
     "warningCount": 0
   },
   "typeCompleteness": {
-    "completenessScore": 0.9202551834130781,
+    "completenessScore": 0.9442675159235668,
     "exportedSymbolCounts": {
       "withAmbiguousType": 0,
-      "withKnownType": 577,
-      "withUnknownType": 50
+      "withKnownType": 593,
+      "withUnknownType": 35
     },
     "ignoreUnknownTypesFromImports": true,
     "missingClassDocStringCount": 1,
@@ -46,17 +46,11 @@
     ],
     "otherSymbolCounts": {
       "withAmbiguousType": 3,
-      "withKnownType": 602,
-      "withUnknownType": 61
+      "withKnownType": 617,
+      "withUnknownType": 50
     },
     "packageName": "trio",
     "symbols": [
-      "trio._core._run.Nursery.start",
-      "trio._core._run.Nursery.start_soon",
-      "trio._core._run.TaskStatus.__repr__",
-      "trio._core._run.TaskStatus.started",
-      "trio._dtls.DTLSChannel.__init__",
-      "trio._dtls.DTLSEndpoint.serve",
       "trio._highlevel_socket.SocketStream.getsockopt",
       "trio._highlevel_socket.SocketStream.send_all",
       "trio._highlevel_socket.SocketStream.setsockopt",
@@ -86,32 +80,22 @@
       "trio._subprocess.Process.send_signal",
       "trio._subprocess.Process.terminate",
       "trio._subprocess.Process.wait",
-      "trio.current_time",
       "trio.from_thread.run",
       "trio.from_thread.run_sync",
       "trio.lowlevel.cancel_shielded_checkpoint",
-      "trio.lowlevel.current_clock",
-      "trio.lowlevel.current_root_task",
-      "trio.lowlevel.current_statistics",
-      "trio.lowlevel.current_trio_token",
       "trio.lowlevel.notify_closing",
       "trio.lowlevel.open_process",
       "trio.lowlevel.permanently_detach_coroutine_object",
       "trio.lowlevel.reattach_detached_coroutine_object",
-      "trio.lowlevel.reschedule",
-      "trio.lowlevel.spawn_system_task",
-      "trio.lowlevel.start_guest_run",
       "trio.lowlevel.temporarily_detach_coroutine_object",
       "trio.lowlevel.wait_readable",
       "trio.lowlevel.wait_writable",
       "trio.open_ssl_over_tcp_listeners",
       "trio.open_ssl_over_tcp_stream",
       "trio.open_unix_socket",
-      "trio.run",
       "trio.run_process",
       "trio.serve_listeners",
       "trio.serve_ssl_over_tcp",
-      "trio.serve_tcp",
       "trio.testing._memory_streams.MemoryReceiveStream.__init__",
       "trio.testing._memory_streams.MemoryReceiveStream.aclose",
       "trio.testing._memory_streams.MemoryReceiveStream.close",
@@ -142,7 +126,6 @@
       "trio.testing.memory_stream_pump",
       "trio.testing.open_stream_to_socket_listener",
       "trio.testing.trio_test",
-      "trio.testing.wait_all_tasks_blocked",
       "trio.tests.TestsDeprecationWrapper",
       "trio.to_thread.current_default_thread_limiter"
     ]

From 876f2e5904ec92db7745c44c4fd0a2a578dfa327 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 11:55:36 +1000
Subject: [PATCH 35/56] Temporarily disable disallow_any_imported

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index d3929662e6..9065d08105 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -71,7 +71,7 @@ disallow_untyped_defs = true
 disallow_untyped_decorators = true
 disallow_any_generics = true
 disallow_any_decorated = true
-disallow_any_unimported = true
+disallow_any_unimported = false  # Enable once outcome has stubs.
 disallow_subclassing_any = true
 
 [[tool.mypy.overrides]]

From 42914e9251990ac2b5467aff845c19509b7af16c Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 12:00:36 +1000
Subject: [PATCH 36/56] Conditionally use the correct IO statistics class for
 RunStatistics

---
 trio/_core/_run.py | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 3b3822a364..e90df3d31d 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1446,12 +1446,6 @@ class RunContext(threading.local):
 GLOBAL_RUN_CONTEXT: FinalT = RunContext()
 
 
-class _IOStats(Protocol):
-    @property
-    def backend(self) -> str:
-        ...
-
-
 @attr.frozen
 class RunStatistics:
     """An object containing run-loop-level debugging information.
@@ -1479,7 +1473,7 @@ class RunStatistics:
     tasks_living: int
     tasks_runnable: int
     seconds_to_next_deadline: float
-    io_statistics: _IOStats
+    io_statistics: IOStatistics
     run_sync_soon_queue_size: int
 
 
@@ -2723,13 +2717,22 @@ async def checkpoint_if_cancelled() -> None:
     from ._io_windows import (
         EventResult as EventResult,
         WindowsIOManager as TheIOManager,
+        _WindowsStatistics as IOStatistics,
     )
 elif sys.platform == "linux" or (not TYPE_CHECKING and hasattr(select, "epoll")):
     from ._generated_io_epoll import *
-    from ._io_epoll import EpollIOManager as TheIOManager, EventResult as EventResult
+    from ._io_epoll import (
+        EpollIOManager as TheIOManager,
+        EventResult as EventResult,
+        _EpollStatistics as IOStatistics,
+    )
 elif TYPE_CHECKING or hasattr(select, "kqueue"):
     from ._generated_io_kqueue import *
-    from ._io_kqueue import EventResult as EventResult, KqueueIOManager as TheIOManager
+    from ._io_kqueue import (
+        EventResult as EventResult,
+        KqueueIOManager as TheIOManager,
+        _KqueueStatistics as IOStatistics,
+    )
 else:  # pragma: no cover
     raise NotImplementedError("unsupported platform")
 

From e339792b13aae53576f2eddc0bdf4ff0bbbb1ffe Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 12:32:16 +1000
Subject: [PATCH 37/56] Fix missing contextvars.Context by manipulating autodoc
 signatures

---
 docs/source/conf.py | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index e4af2b645a..66d058d5f0 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -67,11 +67,11 @@
     ("py:class", "trio._core._run.StatusT_contra"),
     ("py:class", "trio._core._run.RetT"),
     ("py:class", "trio._threads.T"),
-    # why aren't these found in stdlib?
-    ("py:class", "types.FrameType"),
     ("py:class", "P.args"),
     ("py:class", "P.kwargs"),
     ("py:class", "RetT"),
+    # why aren't these found in stdlib?
+    ("py:class", "types.FrameType"),
     # TODO: figure out if you can link this to SSL
     ("py:class", "Context"),
     # TODO: temporary type
@@ -94,6 +94,20 @@
 }
 
 
+def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation):
+    """Modify found signatures to fix various issues."""
+    if 'RunVar' in name:
+        print(locals())
+    if signature is not None:
+        signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
+        if '_NoValue' in signature:
+            # Strip the type from the union, make it look like = ...
+            signature = signature.replace(" | type[trio._core._local._NoValue]", "")
+            signature = signature.replace("<class 'trio._core._local._NoValue'>", "...")
+
+    return signature, return_annotation
+
+
 # XX hack the RTD theme until
 #   https://github.com/rtfd/sphinx_rtd_theme/pull/382
 # is shipped (should be in the release after 0.2.4)
@@ -101,6 +115,7 @@
 # though.
 def setup(app):
     app.add_css_file("hackrtd.css")
+    app.connect("autodoc-process-signature", autodoc_process_signature)
 
 
 # -- General configuration ------------------------------------------------

From d18dbb2e1ff179ee2a99f44145fd2337d5434226 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:25:05 +1000
Subject: [PATCH 38/56] Remove debug code

---
 docs/source/conf.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 66d058d5f0..d050737a03 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -96,8 +96,6 @@
 
 def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation):
     """Modify found signatures to fix various issues."""
-    if 'RunVar' in name:
-        print(locals())
     if signature is not None:
         signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
         if '_NoValue' in signature:

From e2400333a447f23075297957cd511f4fd8f7d73b Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:35:48 +1000
Subject: [PATCH 39/56] This error was globally disabled

---
 trio/_core/_thread_cache.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/trio/_core/_thread_cache.py b/trio/_core/_thread_cache.py
index d8f5a94c5b..8381153576 100644
--- a/trio/_core/_thread_cache.py
+++ b/trio/_core/_thread_cache.py
@@ -118,7 +118,7 @@ def darwin_namefunc(
 
 class WorkerThread(Generic[RetT]):
     def __init__(self, thread_cache: ThreadCache) -> None:
-        self._job: tuple[  # type: ignore[no-any-unimported]
+        self._job: tuple[
             Callable[[], RetT],
             Callable[[outcome.Outcome[RetT]], object],
             str | None,
@@ -198,7 +198,7 @@ class ThreadCache:
     def __init__(self) -> None:
         self._idle_workers: dict[WorkerThread[Any], None] = {}
 
-    def start_thread_soon(  # type: ignore[no-any-unimported]
+    def start_thread_soon(
         self,
         fn: Callable[[], RetT],
         deliver: Callable[[outcome.Outcome[RetT]], object],
@@ -216,7 +216,7 @@ def start_thread_soon(  # type: ignore[no-any-unimported]
 THREAD_CACHE = ThreadCache()
 
 
-def start_thread_soon(  # type: ignore[no-any-unimported]
+def start_thread_soon(
     fn: Callable[[], RetT],
     deliver: Callable[[outcome.Outcome[RetT]], object],
     name: str | None = None,

From a2e48c651e15866d42c488c730939a0ec597ad29 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:39:27 +1000
Subject: [PATCH 40/56] Add types to _io_epoll._EpollStatistics, required by
 type exhaustiveness

---
 trio/_core/_io_epoll.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index 48ee09e3f6..f1c3dc8d51 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -15,15 +15,15 @@
 assert not TYPE_CHECKING or sys.platform == "linux"
 
 if TYPE_CHECKING:
-    from typing_extensions import TypeAlias
+    from typing_extensions import TypeAlias, Literal
 EventResult: TypeAlias = "list[tuple[int, int]]"
 
 
 @attr.s(slots=True, eq=False, frozen=True)
 class _EpollStatistics:
-    tasks_waiting_read = attr.ib()
-    tasks_waiting_write = attr.ib()
-    backend = attr.ib(default="epoll")
+    tasks_waiting_read: int = attr.ib()
+    tasks_waiting_write: int = attr.ib()
+    backend: Literal["epoll"] = attr.ib(default="epoll")
 
 
 # Some facts about epoll

From 0ab9ac7261d187cbe1745b75bfea0856d30b1ebc Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:39:46 +1000
Subject: [PATCH 41/56] See what signature was produced for start_guest_run

---
 docs/source/conf.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index d050737a03..d74eb26366 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -72,6 +72,8 @@
     ("py:class", "RetT"),
     # why aren't these found in stdlib?
     ("py:class", "types.FrameType"),
+    # Outcome isn't being found currently.
+    ("py:class", "Outcome"),
     # TODO: figure out if you can link this to SSL
     ("py:class", "Context"),
     # TODO: temporary type
@@ -98,6 +100,10 @@ def autodoc_process_signature(app, what, name, obj, options, signature, return_a
     """Modify found signatures to fix various issues."""
     if signature is not None:
         signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
+        if name == "trio.lowlevel.start_guest_run":
+            print(locals())
+        if name == "trio.lowlevel.RunVar":  # Typevar is not useful here.
+            signature = signature.replace(": ~trio._core._local.T", "")
         if '_NoValue' in signature:
             # Strip the type from the union, make it look like = ...
             signature = signature.replace(" | type[trio._core._local._NoValue]", "")

From 1cb4efcb7bf769bd5977f3ab43f5d513d2282479 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:43:42 +1000
Subject: [PATCH 42/56] Maybe fix outcome reference?

---
 docs/source/conf.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index d74eb26366..3c1887c89b 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -72,8 +72,6 @@
     ("py:class", "RetT"),
     # why aren't these found in stdlib?
     ("py:class", "types.FrameType"),
-    # Outcome isn't being found currently.
-    ("py:class", "Outcome"),
     # TODO: figure out if you can link this to SSL
     ("py:class", "Context"),
     # TODO: temporary type
@@ -101,7 +99,7 @@ def autodoc_process_signature(app, what, name, obj, options, signature, return_a
     if signature is not None:
         signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
         if name == "trio.lowlevel.start_guest_run":
-            print(locals())
+            signature = signature.replace("Outcome", "~outcome.Outcome")
         if name == "trio.lowlevel.RunVar":  # Typevar is not useful here.
             signature = signature.replace(": ~trio._core._local.T", "")
         if '_NoValue' in signature:

From cbe445964c6600ba1a27cc294aa5e2cee30f0b08 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Wed, 9 Aug 2023 13:46:22 +1000
Subject: [PATCH 43/56] Update type completeness

---
 trio/_tests/verify_types.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json
index 5f368b4cea..50d492d3f1 100644
--- a/trio/_tests/verify_types.json
+++ b/trio/_tests/verify_types.json
@@ -46,7 +46,7 @@
     ],
     "otherSymbolCounts": {
       "withAmbiguousType": 3,
-      "withKnownType": 617,
+      "withKnownType": 627,
       "withUnknownType": 50
     },
     "packageName": "trio",

From 30dccbe6e732b499b4f7928578c61e97456c58c6 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 08:44:50 +1000
Subject: [PATCH 44/56] Fix imports

---
 trio/_core/_generated_instrumentation.py | 4 +++-
 trio/_core/_generated_io_epoll.py        | 4 +++-
 trio/_core/_generated_io_kqueue.py       | 4 +++-
 trio/_core/_generated_io_windows.py      | 4 +++-
 trio/_core/_generated_run.py             | 6 ++++--
 trio/_tools/gen_exports.py               | 4 +++-
 6 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index cf9a6aec65..92e8c9326d 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -4,7 +4,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index 132ed51d81..6b0088f069 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -4,7 +4,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index f87d9d77aa..0d0679b8d6 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -4,7 +4,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 8a7d29240f..62298d1b88 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -4,7 +4,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index db9e516b44..ea3020deda 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -4,7 +4,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 
@@ -112,7 +114,7 @@ def reschedule(task: trio.lowlevel.Task, next_send: Outcome[Any]=_NO_SEND
 
 
 def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
-    Any, name: object=None, context: (contextvars.Context | None)=None
+    object, name: object=None, context: (contextvars.Context | None)=None
     ) ->trio.lowlevel.Task:
     """Spawn a "system" task.
 
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 7046cc50a0..47bc89667f 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -27,7 +27,9 @@
 # isort: skip_file
 from __future__ import annotations
 
-from typing import Any, Callable, Awaitable
+from collections.abc import Callable, Awaitable
+from typing import Any
+
 from outcome import Outcome
 import contextvars
 

From 2add67ea082c552a8e28cc5c492f9b6ba79b11c3 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 10:25:28 +1000
Subject: [PATCH 45/56] Run linters

---
 trio/_core/_io_epoll.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index f1c3dc8d51..0bb4e8a1ed 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -15,7 +15,7 @@
 assert not TYPE_CHECKING or sys.platform == "linux"
 
 if TYPE_CHECKING:
-    from typing_extensions import TypeAlias, Literal
+    from typing_extensions import Literal, TypeAlias
 EventResult: TypeAlias = "list[tuple[int, int]]"
 
 

From 7b7a62b7a5a06365bf77d6115874b2d0ef54aff1 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 17:50:07 +1000
Subject: [PATCH 46/56] Swap Any -> object

---
 trio/_core/_run.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index e90df3d31d..a12f18c7bf 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1136,7 +1136,7 @@ def start_soon(
         self,
         # TODO: TypeVarTuple
         async_fn: Callable[..., Awaitable[object]],
-        *args: Any,
+        *args: object,
         name: object = None,
     ) -> None:
         """Creates a child task, scheduling ``await async_fn(*args)``.
@@ -1695,8 +1695,8 @@ def reschedule(  # type: ignore[misc]
     def spawn_impl(
         self,
         # TODO: TypeVarTuple
-        async_fn: Callable[..., Awaitable[Any]],
-        args: tuple[Any, ...],
+        async_fn: Callable[..., Awaitable[object]],
+        args: tuple[object, ...],
         nursery: Nursery | None,
         name: object,
         *,
@@ -1893,7 +1893,7 @@ async def init(
         # TODO: TypeVarTuple
         self,
         async_fn: Callable[..., Awaitable[object]],
-        args: tuple[Any, ...],
+        args: tuple[object, ...],
     ) -> None:
         # run_sync_soon task runs here:
         async with open_nursery() as run_sync_soon_nursery:
@@ -2267,7 +2267,7 @@ def run(
 
 def start_guest_run(
     async_fn: Callable[..., Awaitable[RetT]],
-    *args: Any,
+    *args: object,
     run_sync_soon_threadsafe: Callable[[Callable[[], object]], object],
     done_callback: Callable[[Outcome[RetT]], object],
     run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object]

From b70139c7dcea3d1e04bc3c8c30dcf67027bcfc02 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 17:50:32 +1000
Subject: [PATCH 47/56] This type: ignore isn't required with new outcome types

---
 trio/_core/_run.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index a12f18c7bf..d46304f724 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1104,8 +1104,7 @@ async def _nested_child_finished(
             # KeyboardInterrupt), then save that, but still wait until our
             # children finish.
             def aborted(raise_cancel: _core.RaiseCancelT) -> Abort:
-                # capture() needs an overload for NoReturn -> Error.
-                self._add_exc(capture(raise_cancel).error)  # type: ignore[union-attr]
+                self._add_exc(capture(raise_cancel).error)
                 return Abort.FAILED
 
             self._parent_waiting_in_aexit = True

From 6645297f720f47b1ada9ff215e5ed10c7eca1e3b Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 17:51:10 +1000
Subject: [PATCH 48/56] Remove these constant attributes from the __init__
 method

---
 trio/_core/_io_epoll.py   |  2 +-
 trio/_core/_io_kqueue.py  |  8 ++++----
 trio/_core/_io_windows.py | 12 ++++++------
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index 0bb4e8a1ed..72e5f5e200 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -23,7 +23,7 @@
 class _EpollStatistics:
     tasks_waiting_read: int = attr.ib()
     tasks_waiting_write: int = attr.ib()
-    backend: Literal["epoll"] = attr.ib(default="epoll")
+    backend: Literal["epoll"] = attr.ib(init=False, default="epoll")
 
 
 # Some facts about epoll
diff --git a/trio/_core/_io_kqueue.py b/trio/_core/_io_kqueue.py
index e9fbbe1c2d..0e7fb4d613 100644
--- a/trio/_core/_io_kqueue.py
+++ b/trio/_core/_io_kqueue.py
@@ -16,15 +16,15 @@
 assert not TYPE_CHECKING or (sys.platform != "linux" and sys.platform != "win32")
 
 if TYPE_CHECKING:
-    from typing_extensions import TypeAlias
+    from typing_extensions import TypeAlias, Literal
 EventResult: TypeAlias = "list[select.kevent]"
 
 
 @attr.s(slots=True, eq=False, frozen=True)
 class _KqueueStatistics:
-    tasks_waiting = attr.ib()
-    monitors = attr.ib()
-    backend = attr.ib(default="kqueue")
+    tasks_waiting: int = attr.ib()
+    monitors: int = attr.ib()
+    backend: Literal["kqueue"] = attr.ib(init=False, default="kqueue")
 
 
 @attr.s(slots=True, eq=False)
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 75784c5ed2..f2b5d41e2f 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -32,7 +32,7 @@
 assert not TYPE_CHECKING or sys.platform == "win32"
 
 if TYPE_CHECKING:
-    from typing_extensions import TypeAlias
+    from typing_extensions import Literal, TypeAlias
 EventResult: TypeAlias = int
 
 # There's a lot to be said about the overall design of a Windows event
@@ -371,11 +371,11 @@ class AFDGroup:
 
 @attr.s(slots=True, eq=False, frozen=True)
 class _WindowsStatistics:
-    tasks_waiting_read = attr.ib()
-    tasks_waiting_write = attr.ib()
-    tasks_waiting_overlapped = attr.ib()
-    completion_key_monitors = attr.ib()
-    backend = attr.ib(default="windows")
+    tasks_waiting_read: int = attr.ib()
+    tasks_waiting_write: int = attr.ib()
+    tasks_waiting_overlapped: int = attr.ib()
+    completion_key_monitors: int = attr.ib()
+    backend: Literal["windows"] = attr.ib(init=False, default="windows")
 
 
 # Maximum number of events to dequeue from the completion port on each pass

From 303abd38bb85168124cf26ad33b4d3fd3a12bc02 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Thu, 10 Aug 2023 17:59:32 +1000
Subject: [PATCH 49/56] More Any -> object

---
 trio/_core/_run.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index d46304f724..bced52d685 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1180,7 +1180,7 @@ def start_soon(
         GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)
 
     async def start(
-        self, async_fn: _NurseryStartFunc[StatusT], *args: Any, name: object = None
+        self, async_fn: _NurseryStartFunc[StatusT], *args: object, name: object = None
     ) -> StatusT:
         r"""Creates and initializes a child task.
 
@@ -1277,7 +1277,7 @@ class Task(metaclass=NoPublicConstructor):
     _next_send_fn: Callable[[Any], object] = attr.ib(default=None)
     _next_send: Outcome[Any] | None | BaseException = attr.ib(default=None)
     _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = attr.ib(default=None)
-    custom_sleep_data: Any = attr.ib(default=None)
+    custom_sleep_data: object = attr.ib(default=None)
 
     # For introspection and nursery.start()
     _child_nurseries: list[Nursery] = attr.ib(factory=list)
@@ -1344,7 +1344,7 @@ def print_stack_for_task(task):
                 print("".join(ss.format()))
 
         """
-        # ignore static typing as we're doing lots of dynamic introspection
+        # Ignore static typing as we're doing lots of dynamic introspection
         coro: Any = self.coro
         while coro is not None:
             if hasattr(coro, "cr_frame"):
@@ -2158,7 +2158,7 @@ def setup_runner(
 
 def run(
     async_fn: Callable[..., RetT],
-    *args: Any,
+    *args: object,
     clock: Clock | None = None,
     instruments: Sequence[Instrument] = (),
     restrict_keyboard_interrupt_to_checkpoints: bool = False,
@@ -2365,7 +2365,7 @@ def my_done_callback(run_outcome):
 def unrolled_run(
     runner: Runner,
     async_fn: Callable[..., object],
-    args: tuple[Any, ...],
+    args: tuple[object, ...],
     host_uses_signal_set_wakeup_fd: bool = False,
 ) -> Generator[float, EventResult, None]:
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True

From 31f4a6a0b13f8f8f56126a223d0e348c63626eb3 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 11 Aug 2023 09:13:33 +1000
Subject: [PATCH 50/56] Switch task._cancel_status type to exclude None, remove
 asserts

---
 trio/_core/_run.py | 24 +++++++-----------------
 1 file changed, 7 insertions(+), 17 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index bced52d685..df622d4255 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -578,17 +578,12 @@ def _close(self, exc: BaseException | None) -> BaseException | None:
                 # CancelStatus.close() will take care of the plumbing;
                 # we just need to make sure we don't let the error
                 # pass silently.
-                scope = (
-                    repr(scope_task._cancel_status._scope)
-                    if scope_task._cancel_status is not None
-                    else "<no scope??>"
-                )
                 new_exc = RuntimeError(
                     "Cancel scope stack corrupted: attempted to exit {!r} "
-                    "in {!r} that's still within its child {}\n{}".format(
+                    "in {!r} that's still within its child {!r}\n{}".format(
                         self,
                         scope_task,
-                        scope,
+                        scope_task._cancel_status._scope,
                         MISNESTING_ADVICE,
                     )
                 )
@@ -1378,12 +1373,13 @@ def print_stack_for_task(task):
 
     # The CancelStatus object that is currently active for this task.
     # Don't change this directly; instead, use _activate_cancel_status().
-    _cancel_status: CancelStatus | None = attr.ib(default=None, repr=False)
+    # This can be None, but only in the init task.
+    _cancel_status: CancelStatus = attr.ib(default=None, repr=False)
 
     def _activate_cancel_status(self, cancel_status: CancelStatus | None) -> None:
         if self._cancel_status is not None:
             self._cancel_status._tasks.remove(self)
-        self._cancel_status = cancel_status
+        self._cancel_status = cancel_status  # type: ignore[assignment]
         if self._cancel_status is not None:
             self._cancel_status._tasks.add(self)
             if self._cancel_status.effectively_cancelled:
@@ -1411,7 +1407,6 @@ def _attempt_abort(self, raise_cancel: _core.RaiseCancelT) -> None:
     def _attempt_delivery_of_any_pending_cancel(self) -> None:
         if self._abort_func is None:
             return
-        assert self._cancel_status is not None
         if not self._cancel_status.effectively_cancelled:
             return
 
@@ -1772,8 +1767,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
 
     def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
         if (
-            task._cancel_status is not None
-            and task._cancel_status.abandoned_by_misnesting
+            task._cancel_status.abandoned_by_misnesting
             and task._cancel_status.parent is None
         ):
             # The cancel scope surrounding this task's nursery was closed
@@ -2653,9 +2647,7 @@ def current_effective_deadline() -> float:
         float: the effective deadline, as an absolute time.
 
     """
-    status = current_task()._cancel_status
-    assert status is not None
-    return status.effective_deadline()
+    return current_task()._cancel_status.effective_deadline()
 
 
 async def checkpoint() -> None:
@@ -2678,7 +2670,6 @@ async def checkpoint() -> None:
     await cancel_shielded_checkpoint()
     task = current_task()
     task._cancel_points += 1
-    assert task._cancel_status is not None, task
     if task._cancel_status.effectively_cancelled or (
         task is task._runner.main_task and task._runner.ki_pending
     ):
@@ -2702,7 +2693,6 @@ async def checkpoint_if_cancelled() -> None:
 
     """
     task = current_task()
-    assert task._cancel_status is not None, task
     if task._cancel_status.effectively_cancelled or (
         task is task._runner.main_task and task._runner.ki_pending
     ):

From 49f87b140137bf9018d4a61ad14bb54ed2eda8ce Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 11 Aug 2023 09:14:21 +1000
Subject: [PATCH 51/56] Some suggested fixes from @oremanj

---
 trio/_core/_run.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index df622d4255..fd66b8dadf 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -40,8 +40,6 @@
 from sniffio import current_async_library_cvar
 from sortedcontainers import SortedDict
 
-import trio._core._ki
-
 from .. import _core
 from .._abc import Clock, Instrument
 from .._util import Final, NoPublicConstructor, coroutine_or_error
@@ -1272,7 +1270,7 @@ class Task(metaclass=NoPublicConstructor):
     _next_send_fn: Callable[[Any], object] = attr.ib(default=None)
     _next_send: Outcome[Any] | None | BaseException = attr.ib(default=None)
     _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = attr.ib(default=None)
-    custom_sleep_data: object = attr.ib(default=None)
+    custom_sleep_data: Any = attr.ib(default=None)
 
     # For introspection and nursery.start()
     _child_nurseries: list[Nursery] = attr.ib(factory=list)
@@ -1633,7 +1631,7 @@ def current_time(self) -> float:
         return self.clock.current_time()
 
     @_public
-    def current_clock(self) -> trio._abc.Clock:
+    def current_clock(self) -> trio.abc.Clock:
         """Returns the current :class:`~trio.abc.Clock`."""
         return self.clock
 

From 857524788f1ecf06a0db157411f6f08ab0400ab0 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 11 Aug 2023 09:29:29 +1000
Subject: [PATCH 52/56] Put this check back

---
 trio/_core/_run.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index fd66b8dadf..7e53119b6c 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1765,6 +1765,7 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
 
     def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
         if (
+            task._cancel_status is not None and
             task._cancel_status.abandoned_by_misnesting
             and task._cancel_status.parent is None
         ):

From 5be051e30845c78dd0b3a447a143826d06ae7c8a Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Fri, 11 Aug 2023 09:29:51 +1000
Subject: [PATCH 53/56] Tidy up imports for generated run

---
 trio/_core/_generated_run.py | 18 +++++++++---------
 trio/_core/_run.py           | 14 +++++++-------
 trio/_tools/gen_exports.py   |  5 +++--
 3 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index 7e00a4a44c..bd5abbd639 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -14,11 +14,12 @@
 from outcome import Outcome
 import contextvars
 
-from ._run import _NO_SEND
-import trio
+from ._run import _NO_SEND, RunStatistics, Task
+from ._entry_queue import TrioToken
+from .._abc import Clock
 
 
-def current_statistics() ->trio.lowlevel.RunStatistics:
+def current_statistics() ->RunStatistics:
     """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
         Currently, the following fields are defined:
@@ -65,7 +66,7 @@ def current_time() ->float:
         raise RuntimeError("must be called from async context")
 
 
-def current_clock() ->trio.abc.Clock:
+def current_clock() ->Clock:
     """Returns the current :class:`~trio.abc.Clock`."""
     locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
     try:
@@ -74,7 +75,7 @@ def current_clock() ->trio.abc.Clock:
         raise RuntimeError("must be called from async context")
 
 
-def current_root_task() ->(trio.lowlevel.Task | None):
+def current_root_task() ->(Task | None):
     """Returns the current root :class:`Task`.
 
         This is the task that is the ultimate parent of all other tasks.
@@ -87,8 +88,7 @@ def current_root_task() ->(trio.lowlevel.Task | None):
         raise RuntimeError("must be called from async context")
 
 
-def reschedule(task: trio.lowlevel.Task, next_send: Outcome[Any]=_NO_SEND
-    ) ->None:
+def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
     """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 
@@ -115,7 +115,7 @@ def reschedule(task: trio.lowlevel.Task, next_send: Outcome[Any]=_NO_SEND
 
 def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
     object, name: object=None, context: (contextvars.Context | None)=None
-    ) ->trio.lowlevel.Task:
+    ) ->Task:
     """Spawn a "system" task.
 
         System tasks have a few differences from regular tasks:
@@ -174,7 +174,7 @@ def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
         raise RuntimeError("must be called from async context")
 
 
-def current_trio_token() ->trio.lowlevel.TrioToken:
+def current_trio_token() ->TrioToken:
     """Retrieve the :class:`TrioToken` for the current call to
         :func:`trio.run`.
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 7e53119b6c..b8f9c2a9bc 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1538,7 +1538,7 @@ def in_main_thread() -> None:
 
 @attr.s(eq=False, hash=False, slots=True)
 class Runner:
-    clock: trio._abc.Clock = attr.ib()
+    clock: Clock = attr.ib()
     instruments: Instruments = attr.ib()
     io_manager: TheIOManager = attr.ib()
     ki_manager: KIManager = attr.ib()
@@ -1585,7 +1585,7 @@ def close(self) -> None:
         self.ki_manager.close()
 
     @_public
-    def current_statistics(self) -> trio.lowlevel.RunStatistics:
+    def current_statistics(self) -> RunStatistics:
         """Returns ``RunStatistics``, which contains run-loop-level debugging information.
 
         Currently, the following fields are defined:
@@ -1631,12 +1631,12 @@ def current_time(self) -> float:
         return self.clock.current_time()
 
     @_public
-    def current_clock(self) -> trio.abc.Clock:
+    def current_clock(self) -> Clock:
         """Returns the current :class:`~trio.abc.Clock`."""
         return self.clock
 
     @_public
-    def current_root_task(self) -> trio.lowlevel.Task | None:
+    def current_root_task(self) -> Task | None:
         """Returns the current root :class:`Task`.
 
         This is the task that is the ultimate parent of all other tasks.
@@ -1650,7 +1650,7 @@ def current_root_task(self) -> trio.lowlevel.Task | None:
 
     @_public  # Type-ignore due to use of Any here.
     def reschedule(  # type: ignore[misc]
-        self, task: trio.lowlevel.Task, next_send: Outcome[Any] = _NO_SEND
+        self, task: Task, next_send: Outcome[Any] = _NO_SEND
     ) -> None:
         """Reschedule the given task with the given
         :class:`outcome.Outcome`.
@@ -1820,7 +1820,7 @@ def spawn_system_task(  # type: ignore[misc]
         *args: object,
         name: object = None,
         context: contextvars.Context | None = None,
-    ) -> trio.lowlevel.Task:
+    ) -> Task:
         """Spawn a "system" task.
 
         System tasks have a few differences from regular tasks:
@@ -1924,7 +1924,7 @@ async def init(
     ################
 
     @_public
-    def current_trio_token(self) -> trio.lowlevel.TrioToken:
+    def current_trio_token(self) -> TrioToken:
         """Retrieve the :class:`TrioToken` for the current call to
         :func:`trio.run`.
 
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 26c51c3896..3c598e8eae 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -253,8 +253,9 @@ def main() -> None:  # pragma: no cover
 from outcome import Outcome
 import contextvars
 
-from ._run import _NO_SEND
-import trio
+from ._run import _NO_SEND, RunStatistics, Task
+from ._entry_queue import TrioToken
+from .._abc import Clock
 """
 IMPORTS_INSTRUMENT = """\
 from ._instrumentation import Instrument

From 792c80b896eabd17fb1f17e512bbec26fcad73e6 Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 12 Aug 2023 11:15:01 +1000
Subject: [PATCH 54/56] Restore removed types

---
 trio/_core/_io_epoll.py   | 4 ++--
 trio/_core/_io_windows.py | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/trio/_core/_io_epoll.py b/trio/_core/_io_epoll.py
index 64730e41c1..0d247cae64 100644
--- a/trio/_core/_io_epoll.py
+++ b/trio/_core/_io_epoll.py
@@ -235,14 +235,14 @@ def force_wakeup(self) -> None:
     # Return value must be False-y IFF the timeout expired, NOT if any I/O
     # happened or force_wakeup was called. Otherwise it can be anything; gets
     # passed straight through to process_events.
-    def get_events(self, timeout) -> EventResult:
+    def get_events(self, timeout: float) -> EventResult:
         # max_events must be > 0 or epoll gets cranky
         # accessing self._registered from a thread looks dangerous, but it's
         # OK because it doesn't matter if our value is a little bit off.
         max_events = max(1, len(self._registered))
         return self._epoll.poll(timeout, max_events)
 
-    def process_events(self, events: EventResult):
+    def process_events(self, events: EventResult) -> None:
         for fd, flags in events:
             if fd == self._force_wakeup_fd:
                 self._force_wakeup.drain()
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 96337323ff..9757d25b5f 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -491,7 +491,7 @@ def force_wakeup(self):
             )
         )
 
-    def get_events(self, timeout) -> EventResult:
+    def get_events(self, timeout: float) -> EventResult:
         received = ffi.new("PULONG")
         milliseconds = round(1000 * timeout)
         if timeout > 0 and milliseconds == 0:
@@ -510,7 +510,7 @@ def get_events(self, timeout) -> EventResult:
         assert isinstance(result, int)
         return result
 
-    def process_events(self, received: EventResult):
+    def process_events(self, received: EventResult) -> None:
         for i in range(received):
             entry = self._events[i]
             if entry.lpCompletionKey == CKeys.AFD_POLL:

From 227d461e19738e76dd12886997e3cd57e24d516f Mon Sep 17 00:00:00 2001
From: Spencer Brown <spencerb21@live.com>
Date: Sat, 12 Aug 2023 11:18:35 +1000
Subject: [PATCH 55/56] Run linters

---
 docs/source/conf.py | 6 ++++--
 trio/_core/_run.py  | 4 ++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 3c1887c89b..06e4a7e6be 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -94,7 +94,9 @@
 }
 
 
-def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation):
+def autodoc_process_signature(
+    app, what, name, obj, options, signature, return_annotation
+):
     """Modify found signatures to fix various issues."""
     if signature is not None:
         signature = signature.replace("~_contextvars.Context", "~contextvars.Context")
@@ -102,7 +104,7 @@ def autodoc_process_signature(app, what, name, obj, options, signature, return_a
             signature = signature.replace("Outcome", "~outcome.Outcome")
         if name == "trio.lowlevel.RunVar":  # Typevar is not useful here.
             signature = signature.replace(": ~trio._core._local.T", "")
-        if '_NoValue' in signature:
+        if "_NoValue" in signature:
             # Strip the type from the union, make it look like = ...
             signature = signature.replace(" | type[trio._core._local._NoValue]", "")
             signature = signature.replace("<class 'trio._core._local._NoValue'>", "...")
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index b8f9c2a9bc..47ba21b158 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1765,8 +1765,8 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
 
     def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
         if (
-            task._cancel_status is not None and
-            task._cancel_status.abandoned_by_misnesting
+            task._cancel_status is not None
+            and task._cancel_status.abandoned_by_misnesting
             and task._cancel_status.parent is None
         ):
             # The cancel scope surrounding this task's nursery was closed

From 137a05b64d678a36c2cc8f7c942bf840002bac32 Mon Sep 17 00:00:00 2001
From: jakkdl <h6+github@pm.me>
Date: Mon, 14 Aug 2023 12:33:07 +0200
Subject: [PATCH 56/56] temp fixes for CI fail in _run until 1. Outcome is
 typed, 2. spawn_impl uses TypeVarTuple, 3. we run mypy without -m

---
 trio/_core/_generated_run.py | 2 +-
 trio/_core/_run.py           | 9 ++++++---
 trio/_tools/gen_exports.py   | 7 +++++++
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index bd5abbd639..35ecd45a1b 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -88,7 +88,7 @@ def current_root_task() ->(Task | None):
         raise RuntimeError("must be called from async context")
 
 
-def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
+def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:  # type: ignore[has-type]
     """Reschedule the given task with the given
         :class:`outcome.Outcome`.
 
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 47ba21b158..1ba88da85e 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1724,7 +1724,8 @@ def spawn_impl(
         # Call the function and get the coroutine object, while giving helpful
         # errors for common mistakes.
         ######
-        coro = context.run(coroutine_or_error, async_fn, *args)
+        # TODO: resolve the type: ignore when implementing TypeVarTuple
+        coro = context.run(coroutine_or_error, async_fn, *args)  # type: ignore[arg-type]
 
         if name is None:
             name = async_fn
@@ -1760,7 +1761,8 @@ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
             self.instruments.call("task_spawned", task)
         # Special case: normally next_send should be an Outcome, but for the
         # very first send we have to send a literal unboxed None.
-        self.reschedule(task, None)  # type: ignore[arg-type]
+        # TODO: remove [unused-ignore] when Outcome is typed
+        self.reschedule(task, None)  # type: ignore[arg-type, unused-ignore]
         return task
 
     def task_exited(self, task: Task, outcome: Outcome[Any]) -> None:
@@ -2554,7 +2556,8 @@ def unrolled_run(
                         # protocol of unwrapping whatever outcome gets sent in.
                         # Instead, we'll arrange to throw `exc` in directly,
                         # which works for at least asyncio and curio.
-                        runner.reschedule(task, exc)  # type: ignore[arg-type]
+                        # TODO: remove [unused-ignore] when Outcome is typed
+                        runner.reschedule(task, exc)  # type: ignore[arg-type, unused-ignore]
                         task._next_send_fn = task.coro.throw
                     # prevent long-lived reference
                     # TODO: develop test for this deletion
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 3c598e8eae..9d78cd5bd7 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -158,6 +158,13 @@ def gen_public_wrappers_source(file: File) -> str:
         if is_cm:  # pragma: no cover
             func = func.replace("->Iterator", "->ContextManager")
 
+        # TODO: hacky workaround until we run mypy without `-m`, which breaks imports
+        # enough that it cannot figure out the type of _NO_SEND
+        if file.path.stem == "_run" and func.startswith(
+            "def reschedule"
+        ):  # pragma: no cover
+            func = func.replace("None:\n", "None:  # type: ignore[has-type]\n")
+
         # Create export function body
         template = TEMPLATE.format(
             " await " if isinstance(method, ast.AsyncFunctionDef) else " ",
