So here is the new code with the @raises decorator: ```python from typing import Callable, NoReturn, ParamSpec, TypeVar, Protocol, cast # ============================================================================== # tooling # ============================================================================== def assert_never(value: NoReturn) -> NoReturn: # This also works at runtime as well assert False, f"This code should never be reached, got: {value}" P = ParamSpec("P") R = TypeVar("R", covariant=True) E = TypeVar("E", bound=tuple) class RaisingFunc(Protocol[P, R, E]): errors: E def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... def raises(errors: E) -> Callable[[Callable[P, R]], RaisingFunc[P, R, E]]: def decorator(func: Callable[P, R]) -> RaisingFunc[P, R, E]: func = cast(RaisingFunc[P, R, E], func) func.errors = errors return func return decorator # ============================================================================== # demo code # ============================================================================== class SomeError(Exception): ... class AnotherError(Exception): ... @raises((ValueError, SomeError, AnotherError)) def foo(i: int): if i == 0: raise ValueError("Must not be zero") elif i == 1: raise SomeError("Must not be one") elif i == 2: raise AnotherError("Must not be two") print("that's ok") @raises((ValueError, AnotherError)) def bar(i: int): try: foo(i) except foo.errors as e: if isinstance(e, SomeError): print("ignore some error") else: raise try: bar(0) except bar.errors as e: if isinstance(e, ValueError): print("value error") elif isinstance(e, AnotherError): print("some error") else: # Here mypy checks for exhaustiveness assert_never(e) raise ``` If the static type checker could be aware of the exceptions that a function can raise, we could imagine such code: ```python class SomeError(Exception): ... class AnotherError(Exception): ... @raises(ValueError, SomeError, AnotherError) def foo(i: int): if i == 0: raise ValueError("Must not be zero") elif i == 1: raise SomeError("Must not be one") elif i == 2: raise AnotherError("Must not be two") print("that's ok") # mypy checks that @raises arguments are correct, as mypy has deduced # from the source code that (ValueError, SomeError, AnotherError) # can be raised. @raises(ValueError, AnotherError) def bar(i: int): try: foo(i) except foo.errors as e: if isinstance(e, SomeError): print("ignore some error") else: raise # mypy checks that @raises arguments are correct, as mypy has deduced # from the source code that (ValueError, AnotherError) can be # raised, because mypy see that foo(i) can raise # (ValueError, SomeError, AnotherError), but SomeError is catched try: bar(0) except ValueError: print("value error") except AnotherError: print("some error") # Here mypy checks for exhaustiveness, as it kwnow that bar(i) can raise # ValueError or AnotherError ``` What do you tkink about this? Thanks!