This rule raise an issue if something other than `None` or a valid exception is provided as the cause of an exception chain.

== Why is this an issue?

Exception chaining enables users to see if an exception is the direct consequence of another exception (see https://peps.python.org/pep-3134/[PEP-3134]). This is useful to propagate the original context of the error.

Exceptions are chained using either of the following syntax:

* With the `from` keyword

[source,python]
----
try:
    ...
except OSError as e:
    raise RuntimeError("Something went wrong") from e
----

* With the ``++__cause__++`` property

[source,python]
----
try:
    ...
except OSError as e:
    new_exception = RuntimeError("Something went wrong")
    new_exception.__cause__ = e
    raise new_exception
----

It is also possible to erase a chaining by setting ``++new_exception.__cause__ = None++`` or using `raise new_exception from None` (see https://peps.python.org/pep-0409/[PEP-409]).


Chaining will fail and raise a `TypeError` if something other than `None` or a valid exception, i.e. an instance or a subclass of `BaseException`, is provided.

== How to fix it

Make sure the cause of a chain of exceptions is either `None` or a valid exception.

=== Code examples

==== Noncompliant code example

[source,python]
----
class A:
    pass

try:
    raise ValueError("orig")
except ValueError as e:
    new_exc = TypeError("new")
    new_exc.__cause__ = A()  # Noncompliant: A is not a subclass of BaseException.
    raise new_exc

try:
    raise ValueError("orig")
except ValueError as e:
    raise TypeError("new") from "test"  # Noncompliant: "test" is not a valid exception.
----


==== Compliant solution

[source,python]
----
try:
    raise ValueError("orig")
except ValueError as e:
    new_exc = TypeError("new")
    new_exc.__cause__ = None  # Ok
    raise new_exc

try:
    raise ValueError("orig")
except ValueError as e:
    new_exc = TypeError("new")
    new_exc.__cause__ = e  # Ok
    raise new_exc

try:
    raise ValueError("orig")
except ValueError as e:
    raise TypeError("new") from None  # Ok

try:
    raise ValueError("orig")
except ValueError as e:
    raise TypeError("new") from e  # Ok
----


== Resources

=== Documentation

* https://docs.python.org/3/library/exceptions.html[Built-in Exceptions]

=== Standards

* https://peps.python.org/pep-3134/[Exception Chaining and Embedded Tracebacks] - PEP 3134 
* https://peps.python.org/pep-0409/[Suppressing exception context] - PEP 409 
* https://peps.python.org/pep-0352/#exception-hierarchy-changes[Required Superclass for Exceptions] - PEP 352 

ifdef::env-github,rspecator-view[]

'''
== Implementation Specification
(visible only on this page)

=== Message

Replace this expression of type X with an exception or None


=== Highlighting

* In a "raise X from Y" statement:
** highlight Y
* In an "myexception.__cause__ = Y" statement:
** highlight Y


endif::env-github,rspecator-view[]
