From 4cdf6d3941b2ed4283ce34a78c2db8445f66fe15 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 23 Jan 2022 16:35:29 -0800 Subject: [PATCH 1/6] bpo-46480: add typing.assert_type --- Doc/library/typing.rst | 15 +++++++++++++++ Lib/test/test_typing.py | 17 ++++++++++++++++- Lib/typing.py | 17 +++++++++++++++++ .../2022-01-23-16-33-07.bpo-46480.E4jHlh.rst | 1 + 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2022-01-23-16-33-07.bpo-46480.E4jHlh.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index cdfd403a34ef91..93d69ec60b4646 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1932,6 +1932,21 @@ Functions and decorators runtime we intentionally don't check anything (we want this to be as fast as possible). +.. function:: assert_type(val, typ, /) + + Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to ``assert_type()``, it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged. + + .. versionadded:: 3.11 + .. decorator:: overload The ``@overload`` decorator allows describing functions and methods diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 150d7c081c30b6..631c57ae7bc725 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -16,7 +16,7 @@ from typing import Tuple, List, Dict, MutableMapping from typing import Callable from typing import Generic, ClassVar, Final, final, Protocol -from typing import cast, runtime_checkable +from typing import assert_type, cast, runtime_checkable from typing import get_type_hints from typing import get_origin, get_args from typing import is_typeddict @@ -2639,6 +2639,21 @@ def test_errors(self): cast('hello', 42) +class AssertTypeTests(BaseTestCase): + + def test_basics(self): + arg = 42 + self.assertIs(assert_type(arg, int), arg) + self.assertIs(assert_type(arg, str | float), arg) + self.assertIs(assert_type(arg, AnyStr), arg) + self.assertIs(assert_type(arg, None), arg) + + def test_errors(self): + # Bogus calls are not expected to fail. + assert_type(42, 42) + assert_type(42, 'hello') + + class ForwardRefTests(BaseTestCase): def test_basics(self): diff --git a/Lib/typing.py b/Lib/typing.py index 972b8ba24b27e8..9f2015984647f0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -117,6 +117,7 @@ def _idfunc(_, x): # One-off things. 'AnyStr', + 'assert_type', 'cast', 'final', 'get_args', @@ -1722,6 +1723,22 @@ def cast(typ, val): return val +def assert_type(val, typ, /): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged. + + """ + return val + + def _get_defaults(func): """Internal helper to extract the default arguments, by name.""" try: diff --git a/Misc/NEWS.d/next/Library/2022-01-23-16-33-07.bpo-46480.E4jHlh.rst b/Misc/NEWS.d/next/Library/2022-01-23-16-33-07.bpo-46480.E4jHlh.rst new file mode 100644 index 00000000000000..fd18a8198edea0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-23-16-33-07.bpo-46480.E4jHlh.rst @@ -0,0 +1 @@ +Add :func:`typing.assert_type`. Patch by Jelle Zijlstra. From 3b09f1b44e7c9df120b47e701c71a35979779bf6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 1 Feb 2022 21:05:21 -0800 Subject: [PATCH 2/6] feedback from David Foster --- Doc/library/typing.rst | 3 ++- Lib/test/test_typing.py | 5 +++-- Lib/typing.py | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 348f2e5d3ff4f0..b3034e8c1a62a8 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1943,7 +1943,8 @@ Functions and decorators assert_type(name, str) # ok assert_type(name, int) # type checker error - At runtime this returns the first argument unchanged. + At runtime this returns the first argument unchanged and otherwise + does nothing. .. versionadded:: 3.11 diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d5232d5448076e..e3f1cefb6f3691 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2681,8 +2681,9 @@ def test_basics(self): def test_errors(self): # Bogus calls are not expected to fail. - assert_type(42, 42) - assert_type(42, 'hello') + arg = 42 + self.assertIs(assert_type(arg, 42), arg) + self.assertIs(assert_type(arg, 'hello'), arg) class ForwardRefTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index 8153c1fb74a541..e39db937f53f94 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1742,7 +1742,8 @@ def greet(name: str) -> None: assert_type(name, str) # ok assert_type(name, int) # type checker error - At runtime this returns the first argument unchanged. + At runtime this returns the first argument unchanged and otherwise + does nothing. """ return val From ba35012d35f6eede6084273e0340a99d2b389c2f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 11 Mar 2022 19:20:47 -0800 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/library/typing.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 526d606c189451..01f232614caf51 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2128,6 +2128,16 @@ Functions and decorators At runtime this returns the first argument unchanged and otherwise does nothing. + This function is useful for ensuring the type checker's understanding of a + script is in line with the developer's intentions:: + + def complex_function(arg: object): + # Do some complex type-narrowing logic, + # after which we hope the inferred type will be `int` + ... + # Test whether the type checker correctly understands our function + assert_type(arg, int) + .. versionadded:: 3.11 .. function:: assert_never(arg, /) From 49765d3e478886dc687af3abb025150f9f39e254 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 11 Mar 2022 19:21:41 -0800 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/library/typing.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 01f232614caf51..4c86bb7ec6f4bb 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2116,17 +2116,16 @@ Functions and decorators .. function:: assert_type(val, typ, /) - Assert (to the type checker) that the value is of the given type. + Assert (to the type checker) that *value* has an inferred type of *type*. When the type checker encounters a call to ``assert_type()``, it emits an error if the value is not of the specified type:: def greet(name: str) -> None: - assert_type(name, str) # ok + assert_type(name, str) # OK, inferred type of `name` is `str` assert_type(name, int) # type checker error - At runtime this returns the first argument unchanged and otherwise - does nothing. + At runtime this returns the first argument unchanged with no side effects. This function is useful for ensuring the type checker's understanding of a script is in line with the developer's intentions:: From b2c4cc5d858bcfad9e62a4e3276f3afaf4f9896b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 12 Mar 2022 10:03:50 -0800 Subject: [PATCH 5/6] Update Lib/typing.py Co-authored-by: David Foster --- Lib/typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 04a14af45b1770..6930f5ddac42ac 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2106,7 +2106,6 @@ def greet(name: str) -> None: At runtime this returns the first argument unchanged and otherwise does nothing. - """ return val From e96c988d85ab3dae5cbe3157be1d697f5acee6c7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 12 Mar 2022 15:19:13 -0800 Subject: [PATCH 6/6] Update Doc/library/typing.rst Co-authored-by: Alex Waygood --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 4c86bb7ec6f4bb..ce4fbd061f05c2 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2116,7 +2116,7 @@ Functions and decorators .. function:: assert_type(val, typ, /) - Assert (to the type checker) that *value* has an inferred type of *type*. + Assert (to the type checker) that *val* has an inferred type of *typ*. When the type checker encounters a call to ``assert_type()``, it emits an error if the value is not of the specified type::