From ce3da534634d5494c37b68457eb5ad1774b53e8e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 19 Apr 2022 19:50:17 -0700 Subject: [PATCH 1/4] gh-90633: Improve error and docs for typing.assert_never --- Doc/library/typing.rst | 5 +++++ Lib/test/test_typing.py | 13 +++++++++++++ Lib/typing.py | 8 +++++++- .../2022-04-19-19-50-10.gh-issue-90633.Youov0.rst | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2022-04-19-19-50-10.gh-issue-90633.Youov0.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 6b2a0934171a29..8ae28d56b39409 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2346,6 +2346,11 @@ Functions and decorators At runtime, this throws an exception when called. + .. seealso:: + `Unreachable Code and Exhaustiveness Checking + _` has more + information about exhaustiveness checking with static typing. + .. versionadded:: 3.11 .. function:: reveal_type(obj) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d4808474e4fcee..f2a62ee213d062 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -211,6 +211,19 @@ def test_exception(self): 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): diff --git a/Lib/typing.py b/Lib/typing.py index 3e0fbdb9891557..b5c087e1d2c727 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2369,6 +2369,9 @@ class Film(TypedDict): 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. @@ -2389,7 +2392,10 @@ def int_or_str(arg: int | str) -> None: 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 index 00000000000000..d86c2d3ff5de46 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-19-19-50-10.gh-issue-90633.Youov0.rst @@ -0,0 +1,2 @@ +Include the passed value in the exception thrown by +:func:`typing.assert_never`. Patch by Jelle Zijlstra. From 8cbf8ac2f3ec7f46ec9f59c7afc31232afd12644 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 20 Apr 2022 19:01:05 -0700 Subject: [PATCH 2/4] expand the docs --- Doc/library/typing.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 8ae28d56b39409..e6544bee562e17 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2341,8 +2341,14 @@ Functions and decorators case _ as unreachable: assert_never(unreachable) + Here, the type annotations allow the type checker to infer that the + last case can never execute, because ``arg`` is either + an :type:`int` or a :type:`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 :type:`float`. At runtime, this throws an exception when called. From a75280c4eb40349c4ad21a72c05788999a3b4731 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 20 Apr 2022 19:11:23 -0700 Subject: [PATCH 3/4] Fix roles --- Doc/library/typing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index e6544bee562e17..5c08cc9751f3a4 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2343,12 +2343,12 @@ Functions and decorators Here, the type annotations allow the type checker to infer that the last case can never execute, because ``arg`` is either - an :type:`int` or a :type:`str`, and both options are covered by + 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. 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 :type:`float`. + emit an error pointing out that ``unreachable`` is of type :class:`float`. At runtime, this throws an exception when called. From 8d63329dbcb125bb476703e2cb67423870022a4e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 24 Apr 2022 15:13:13 -0700 Subject: [PATCH 4/4] Apply suggestions from code review Thanks Alex! Co-authored-by: Alex Waygood --- Doc/library/typing.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 5c08cc9751f3a4..217b2d221c6743 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2341,7 +2341,7 @@ Functions and decorators case _ as unreachable: assert_never(unreachable) - Here, the type annotations allow the type checker to infer that the + 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. @@ -2349,6 +2349,9 @@ Functions and decorators 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.