]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-90633: Improve error and docs for typing.assert_never (#91720)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Mon, 25 Apr 2022 13:40:18 +0000 (06:40 -0700)
committerGitHub <noreply@github.com>
Mon, 25 Apr 2022 13:40:18 +0000 (06:40 -0700)
Closes #90633

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Doc/library/typing.rst
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2022-04-19-19-50-10.gh-issue-90633.Youov0.rst [new file with mode: 0644]

index 2f62193e65c293659a29a991086c7df22fb3b1d9..4635da7579ac75d116b1d049ca72ebc16ce207ed 100644 (file)
@@ -2345,11 +2345,25 @@ Functions and decorators
                case _ as unreachable:
                    assert_never(unreachable)
 
+   Here, the annotations allow the type checker to infer that the
+   last case can never execute, because ``arg`` is either
+   an :class:`int` or a :class:`str`, and both options are covered by
+   earlier cases.
    If a type checker finds that a call to ``assert_never()`` is
-   reachable, it will emit an error.
+   reachable, it will emit an error. For example, if the type annotation
+   for ``arg`` was instead ``int | str | float``, the type checker would
+   emit an error pointing out that ``unreachable`` is of type :class:`float`.
+   For a call to ``assert_never`` to succeed, the inferred type of
+   the argument passed in must be the bottom type, :data:`Never`, and nothing
+   else.
 
    At runtime, this throws an exception when called.
 
+   .. seealso::
+      `Unreachable Code and Exhaustiveness Checking
+      <https://typing.readthedocs.io/en/latest/source/unreachable.html>_` has more
+      information about exhaustiveness checking with static typing.
+
    .. versionadded:: 3.11
 
 .. function:: reveal_type(obj)
index 1fd99a0f806cf0327f51ab121dfc3d234a2def3d..fce340f27d9aca6b21b2db7488f05e2efa18a9c4 100644 (file)
@@ -223,6 +223,19 @@ class AssertNeverTests(BaseTestCase):
         with self.assertRaises(AssertionError):
             assert_never(None)
 
+        value = "some value"
+        with self.assertRaisesRegex(AssertionError, value):
+            assert_never(value)
+
+        # Make sure a huge value doesn't get printed in its entirety
+        huge_value = "a" * 10000
+        with self.assertRaises(AssertionError) as cm:
+            assert_never(huge_value)
+        self.assertLess(
+            len(cm.exception.args[0]),
+            typing._ASSERT_NEVER_REPR_MAX_LENGTH * 2,
+        )
+
 
 class SelfTests(BaseTestCase):
     def test_equality(self):
index a6f4fa96afb50504a920c91b375516378a1b3d5b..9d8149c3f5332d94beff897e1fc8e01c8bebd4e3 100644 (file)
@@ -2382,6 +2382,9 @@ def is_typeddict(tp):
     return isinstance(tp, _TypedDictMeta)
 
 
+_ASSERT_NEVER_REPR_MAX_LENGTH = 100
+
+
 def assert_never(arg: Never, /) -> Never:
     """Statically assert that a line of code is unreachable.
 
@@ -2402,7 +2405,10 @@ def assert_never(arg: Never, /) -> Never:
     At runtime, this throws an exception when called.
 
     """
-    raise AssertionError("Expected code to be unreachable")
+    value = repr(arg)
+    if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
+        value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...'
+    raise AssertionError(f"Expected code to be unreachable, but got: {value}")
 
 
 def no_type_check(arg):
diff --git a/Misc/NEWS.d/next/Library/2022-04-19-19-50-10.gh-issue-90633.Youov0.rst b/Misc/NEWS.d/next/Library/2022-04-19-19-50-10.gh-issue-90633.Youov0.rst
new file mode 100644 (file)
index 0000000..d86c2d3
--- /dev/null
@@ -0,0 +1,2 @@
+Include the passed value in the exception thrown by
+:func:`typing.assert_never`. Patch by Jelle Zijlstra.