Repository: python-trio/trio

Issue:
`sleep_forever` should be typed NoReturn
I think this would be more correct? the trio-typing stubs did this: https://github.com/python-trio/trio-typing/blob/c7d97072e630cced81cb8a7bb30c0707f1a5d235/trio-stubs/__init__.pyi#L198

Discussion:
@A5rocks: Good catch! Feel free to make a PR.@TeamSpen210: This is definitely correct, but I did notice a slight issue. It seems PyCharm [has a bug](https://youtrack.jetbrains.com/issue/PY-73486/Code-marked-as-unreachable-when-using-NoReturn-annotation-in-context-manager-capturing-exception) where context managers won't be taken into account for unreachability. So the common case of a cancel-scope around `sleep_forever` will give spurious unreachable warnings. 

Not sure if that is sufficient to prevent changing this.@CoolCat467: I personally don't think we should avoid changing this just because an IDE's builtin type checker has bugs. If the type checker was separate from the IDE that might be a different story, but I'm sure they can fix this eventually.

Solution patch:
diff --git a/newsfragments/3095.bugfix.rst b/newsfragments/3095.bugfix.rst
new file mode 100644
index 000000000..7a7d756e5
--- /dev/null
+++ b/newsfragments/3095.bugfix.rst
@@ -0,0 +1 @@
+Update :func:`trio.sleep_forever` to be `NoReturn`.
diff --git a/src/trio/_timeouts.py b/src/trio/_timeouts.py
index 1a5e2e1db..417f1818f 100644
--- a/src/trio/_timeouts.py
+++ b/src/trio/_timeouts.py
@@ -3,7 +3,7 @@
 import math
 import sys
 from contextlib import contextmanager
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, NoReturn
 
 import trio
 
@@ -58,13 +58,14 @@ def move_on_after(
     )
 
 
-async def sleep_forever() -> None:
+async def sleep_forever() -> NoReturn:
     """Pause execution of the current task forever (or until cancelled).
 
     Equivalent to calling ``await sleep(math.inf)``.
 
     """
     await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
+    raise RuntimeError("Should never have been rescheduled!")
 
 
 async def sleep_until(deadline: float) -> None:


Test patch:
diff --git a/src/trio/_tests/test_timeouts.py b/src/trio/_tests/test_timeouts.py
index 515f536c1..574e1db1a 100644
--- a/src/trio/_tests/test_timeouts.py
+++ b/src/trio/_tests/test_timeouts.py
@@ -86,6 +86,20 @@ async def sleep_3() -> None:
     await check_takes_about(sleep_3, TARGET)
 
 
+async def test_cannot_wake_sleep_forever() -> None:
+    # Test an error occurs if you manually wake sleep_forever().
+    task = trio.lowlevel.current_task()
+
+    async def wake_task() -> None:
+        await trio.lowlevel.checkpoint()
+        trio.lowlevel.reschedule(task, outcome.Value(None))
+
+    async with trio.open_nursery() as nursery:
+        nursery.start_soon(wake_task)
+        with pytest.raises(RuntimeError):
+            await trio.sleep_forever()
+
+
 class TimeoutScope(Protocol):
     def __call__(self, seconds: float, *, shield: bool) -> trio.CancelScope: ...