Proposal: typing.assert_never()

Another idea that came up in a few recent threads is a `typing.assert_never()` function, which indicates that some code is supposed to be unreachable. I will submit an implementation to CPython, and we can use this thread to work out any details (or to let me know that we shouldn't do this after all). # Specification We will add a new function `typing.assert_never()` with the following signature: def assert_never(arg: NoReturn, /) -> NoReturn: ... At runtime, this will unconditionally raise `RuntimeError(f"Unexpectedly reached {arg!r}")` (wording suggestions welcome). Type checkers should emit an error if the function is called with an argument that has a non-bottom type. Examples: match bool(): case True: pass case False: pass case _ as x: assert_never(x) # ok match int(): case 0: pass case 1: pass case _ as x: assert_never(x) # error: x is an int, not NoReturn # Motivation PEP 484 only allows NoReturn as a return type, but in practice all type checkers allow it in argument positions as a bottom type. Thus, a manually defined `assert_never()` function already has the behavior specified above. Adding the function to the standard library will make this behavior more discoverable for users. Relatedly, I opened https://bugs.python.org/issue46475 to document NoReturn being a general bottom type. # Open questions Suggestions welcome for better wording in the runtime error. There is a related proposal to rename NoReturn to Never. I'm lukewarm about that because renaming things always causes churn, but in any case let's discuss that separately.

There is a related proposal to rename NoReturn to Never.
The proposal is to create an alias of `NoReturn` called `Never` (or vice verse). In other words, the existing `NoReturn` would be preserved as a type, and `Never` would be another name for it. This would allow `NoReturn` to be be used in contexts where that name makes sense (namely, as a function return type annotation for a function that never returns), and `Never` could be used in contexts where it makes sense (such as in `assert_never`). Type checkers could even choose to enforce that `NoReturn` be used only in the proper context, enforcing the "letter of the law" in PEP 484. I'd really like to see this introduced along with `assert_never`. The two go hand in hand, IMO. -Eric

I agree with Eric. A `Never` alias for `NoReturn` and an `assert_never()` function are both great ideas. Best, Alex
On 22 Jan 2022, at 23:02, Eric Traut <eric@traut.com> wrote:
There is a related proposal to rename NoReturn to Never.
The proposal is to create an alias of `NoReturn` called `Never` (or vice verse). In other words, the existing `NoReturn` would be preserved as a type, and `Never` would be another name for it. This would allow `NoReturn` to be be used in contexts where that name makes sense (namely, as a function return type annotation for a function that never returns), and `Never` could be used in contexts where it makes sense (such as in `assert_never`). Type checkers could even choose to enforce that `NoReturn` be used only in the proper context, enforcing the "letter of the law" in PEP 484.
I'd really like to see this introduced along with `assert_never`. The two go hand in hand, IMO.
-Eric _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: alex.waygood@gmail.com

On 1/22/22 2:51 PM, Jelle Zijlstra wrote:
def assert_never(arg: NoReturn, /) -> NoReturn: ...
At runtime, this will unconditionally raise `RuntimeError(f"Unexpectedly reached {arg!r}")` (wording suggestions welcome).
It seems to me that assert_never() might likely be used in the context of parsing/validation code that is examining untrusted input. Automatically invoking code derived from untrusted input (like `repr(arg)` or `str(arg)`) feels unsafe to me. I would suggest raising a fixed error message, and in particular not running arbitrary code related to `arg` such as `repr(arg)` or `str(arg)`. On 1/22/22 3:52 PM, Alex Waygood wrote:
I agree with Eric. A `Never` alias for `NoReturn` and an `assert_never()` function are both great ideas.
+1 from me as well -- David Foster | Seattle, WA, USA Contributor to TypedDict support for mypy

I put up a draft implementation at https://github.com/python/cpython/pull/30842. Based on the feedback here, I am also proposing to add typing.Never as an alternative spelling for NoReturn. El sáb, 22 ene 2022 a las 14:51, Jelle Zijlstra (<jelle.zijlstra@gmail.com>) escribió:
Another idea that came up in a few recent threads is a `typing.assert_never()` function, which indicates that some code is supposed to be unreachable. I will submit an implementation to CPython, and we can use this thread to work out any details (or to let me know that we shouldn't do this after all).
# Specification
We will add a new function `typing.assert_never()` with the following signature:
def assert_never(arg: NoReturn, /) -> NoReturn: ...
At runtime, this will unconditionally raise `RuntimeError(f"Unexpectedly reached {arg!r}")` (wording suggestions welcome).
Type checkers should emit an error if the function is called with an argument that has a non-bottom type. Examples:
match bool(): case True: pass case False: pass case _ as x: assert_never(x) # ok
match int(): case 0: pass case 1: pass case _ as x: assert_never(x) # error: x is an int, not NoReturn
# Motivation
PEP 484 only allows NoReturn as a return type, but in practice all type checkers allow it in argument positions as a bottom type. Thus, a manually defined `assert_never()` function already has the behavior specified above. Adding the function to the standard library will make this behavior more discoverable for users.
Relatedly, I opened https://bugs.python.org/issue46475 to document NoReturn being a general bottom type.
# Open questions
Suggestions welcome for better wording in the runtime error.
There is a related proposal to rename NoReturn to Never. I'm lukewarm about that because renaming things always causes churn, but in any case let's discuss that separately.

Grepping for assert_never in public code shows that people usually print the type of the object. https://grep.app/search?q=def%20assert_never%28&case=true&filter[lang][0]=Python I propose `raise RuntimeError(f"Unexpectedly reached {type(arg).__name__}")`. I'm not sure I understand the vector for concern about untrusted input, but I believe printing the type should be fine; if you have an untrusted type in your program you have previous problems. On Sun, 23 Jan 2022 at 15:42, Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
I put up a draft implementation at https://github.com/python/cpython/pull/30842. Based on the feedback here, I am also proposing to add typing.Never as an alternative spelling for NoReturn.
El sáb, 22 ene 2022 a las 14:51, Jelle Zijlstra (<jelle.zijlstra@gmail.com>) escribió:
Another idea that came up in a few recent threads is a `typing.assert_never()` function, which indicates that some code is supposed to be unreachable. I will submit an implementation to CPython, and we can use this thread to work out any details (or to let me know that we shouldn't do this after all).
# Specification
We will add a new function `typing.assert_never()` with the following signature:
def assert_never(arg: NoReturn, /) -> NoReturn: ...
At runtime, this will unconditionally raise `RuntimeError(f"Unexpectedly reached {arg!r}")` (wording suggestions welcome).
Type checkers should emit an error if the function is called with an argument that has a non-bottom type. Examples:
match bool(): case True: pass case False: pass case _ as x: assert_never(x) # ok
match int(): case 0: pass case 1: pass case _ as x: assert_never(x) # error: x is an int, not NoReturn
# Motivation
PEP 484 only allows NoReturn as a return type, but in practice all type checkers allow it in argument positions as a bottom type. Thus, a manually defined `assert_never()` function already has the behavior specified above. Adding the function to the standard library will make this behavior more discoverable for users.
Relatedly, I opened https://bugs.python.org/issue46475 to document NoReturn being a general bottom type.
# Open questions
Suggestions welcome for better wording in the runtime error.
There is a related proposal to rename NoReturn to Never. I'm lukewarm about that because renaming things always causes churn, but in any case let's discuss that separately.
_______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: hauntsaninja@gmail.com

On 1/23/22 5:49 PM, Shantanu Jain wrote:
I'm not sure I understand the vector for concern about untrusted input, but I believe printing the type should be fine; if you have an untrusted type in your program you have previous problems.
This is certainly safer. There's still potentially the risk of log injection here. Specifically if it is possible to create a type whose name contains newlines - such as via the type() function - then you can potentially inject arbitrary log lines in Python services that don't quote log output properly. But as you mention, to get an untrusted type in that fashion you'd already need an attack that gets the ability to eval() Python. -- David Foster | Seattle, WA, USA Contributor to TypedDict support for mypy

@Jelle I really like this proposal, both the Never and the assert_never() parts. Some bike-shedding: there's an existing issue on the typing repository for this util, I added a comment there linking back to this thread and your PR. On that issue, it's proposed to call the helper assert_exhaustive(). I'd be happy with the tool in the stdlib either way, but I do feel like that name is better as it would be more likely to lead someone looking it up towards the concept of exhaustiveness checking.

Support for assert_never and Never was merged into CPython today. Both will exist in Python 3.11. I expect them to also be in the next release of typing-extensions. Anton, we can still change the name if there's consensus here, but I'd prefer to keep the assert_never name. `assert_never(arg)` means "assert that arg is of type Never", not "assert that arg is exhaustive". We can mention "exhaustiveness checking" as a term in related documentation, though. El mar, 8 feb 2022 a las 5:54, Anton Agestam (<git@antonagestam.se>) escribió:
@Jelle I really like this proposal, both the Never and the assert_never() parts. Some bike-shedding: there's an existing issue on the typing repository for this util, I added a comment there linking back to this thread and your PR. On that issue, it's proposed to call the helper assert_exhaustive(). I'd be happy with the tool in the stdlib either way, but I do feel like that name is better as it would be more likely to lead someone looking it up towards the concept of exhaustiveness checking. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: jelle.zijlstra@gmail.com
participants (6)
-
Alex Waygood
-
Anton Agestam
-
David Foster
-
Eric Traut
-
Jelle Zijlstra
-
Shantanu Jain