mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-06 11:48:08 +00:00

## Summary This PR closes https://github.com/astral-sh/ty/issues/238. Since `DefinitionState::Deleted` was introduced in #18041, support for the `del` statement (and deletion of except handler names) is straightforward. However, it is difficult to determine whether references to attributes or subscripts are unresolved after they are deleted. This PR only invalidates narrowing by assignment if the attribute or subscript is deleted. ## Test Plan `mdtest/del.md` is added. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
6 KiB
6 KiB
Exception Handling
Single Exception
import re
try:
help()
except NameError as e:
reveal_type(e) # revealed: NameError
except re.error as f:
reveal_type(f) # revealed: error
Unknown type in except handler does not cause spurious diagnostic
from nonexistent_module import foo # error: [unresolved-import]
try:
help()
except foo as e:
reveal_type(foo) # revealed: Unknown
reveal_type(e) # revealed: Unknown
Multiple Exceptions in a Tuple
EXCEPTIONS = (AttributeError, TypeError)
try:
help()
except (RuntimeError, OSError) as e:
reveal_type(e) # revealed: RuntimeError | OSError
except EXCEPTIONS as f:
reveal_type(f) # revealed: AttributeError | TypeError
Dynamic exception types
def foo(
x: type[AttributeError],
y: tuple[type[OSError], type[RuntimeError]],
z: tuple[type[BaseException], ...],
zz: tuple[type[TypeError | RuntimeError], ...],
zzz: type[BaseException] | tuple[type[BaseException], ...],
):
try:
help()
except x as e:
reveal_type(e) # revealed: AttributeError
except y as f:
reveal_type(f) # revealed: OSError | RuntimeError
except z as g:
reveal_type(g) # revealed: BaseException
except zz as h:
reveal_type(h) # revealed: TypeError | RuntimeError
except zzz as i:
reveal_type(i) # revealed: BaseException
We do not emit an invalid-exception-caught
if a class is caught that has Any
or Unknown
in its
MRO, as the dynamic element in the MRO could materialize to some subclass of BaseException
:
from compat import BASE_EXCEPTION_CLASS # error: [unresolved-import] "Cannot resolve imported module `compat`"
class Error(BASE_EXCEPTION_CLASS): ...
try:
...
except Error as err:
...
Exception with no captured type
try:
{}.get("foo")
except TypeError:
pass
Exception which catches typevar
[environment]
python-version = "3.12"
from typing import Callable
def silence[T: type[BaseException]](
func: Callable[[], None],
exception_type: T,
):
try:
func()
except exception_type as e:
reveal_type(e) # revealed: T'instance
def silence2[T: (
type[ValueError],
type[TypeError],
)](func: Callable[[], None], exception_type: T,):
try:
func()
except exception_type as e:
reveal_type(e) # revealed: T'instance
Invalid exception handlers
try:
pass
# error: [invalid-exception-caught] "Cannot catch object of type `Literal[3]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
except 3 as e:
reveal_type(e) # revealed: Unknown
try:
pass
# error: [invalid-exception-caught] "Cannot catch object of type `Literal["foo"]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
# error: [invalid-exception-caught] "Cannot catch object of type `Literal[b"bar"]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
except (ValueError, OSError, "foo", b"bar") as e:
reveal_type(e) # revealed: ValueError | OSError | Unknown
def foo(
x: type[str],
y: tuple[type[OSError], type[RuntimeError], int],
z: tuple[type[str], ...],
):
try:
help()
# error: [invalid-exception-caught]
except x as e:
reveal_type(e) # revealed: Unknown
# error: [invalid-exception-caught]
except y as f:
reveal_type(f) # revealed: OSError | RuntimeError | Unknown
# error: [invalid-exception-caught]
except z as g:
reveal_type(g) # revealed: Unknown
try:
{}.get("foo")
# error: [invalid-exception-caught]
except int:
pass
Object raised is not an exception
try:
raise AttributeError() # fine
except:
...
try:
raise FloatingPointError # fine
except:
...
try:
raise 1 # error: [invalid-raise]
except:
...
try:
raise int # error: [invalid-raise]
except:
...
def _(e: Exception | type[Exception]):
raise e # fine
def _(e: Exception | type[Exception] | None):
raise e # error: [invalid-raise]
Exception cause is not an exception
def _():
try:
raise EOFError() from GeneratorExit # fine
except:
...
def _():
try:
raise StopIteration from MemoryError() # fine
except:
...
def _():
try:
raise BufferError() from None # fine
except:
...
def _():
try:
raise ZeroDivisionError from False # error: [invalid-raise]
except:
...
def _():
try:
raise SystemExit from bool() # error: [invalid-raise]
except:
...
def _():
try:
raise
except KeyboardInterrupt as e: # fine
reveal_type(e) # revealed: KeyboardInterrupt
raise LookupError from e # fine
def _():
try:
raise
except int as e: # error: [invalid-exception-caught]
reveal_type(e) # revealed: Unknown
raise KeyError from e
def _(e: Exception | type[Exception]):
raise ModuleNotFoundError from e # fine
def _(e: Exception | type[Exception] | None):
raise IndexError from e # fine
def _(e: int | None):
raise IndexError from e # error: [invalid-raise]
The caught exception is cleared at the end of the except clause
e = None
reveal_type(e) # revealed: None
try:
raise ValueError()
except ValueError as e:
reveal_type(e) # revealed: ValueError
# error: [unresolved-reference]
reveal_type(e) # revealed: Unknown
e = None
def cond() -> bool:
return True
try:
if cond():
raise ValueError()
except ValueError as e:
reveal_type(e) # revealed: ValueError
# error: [possibly-unresolved-reference]
reveal_type(e) # revealed: None
def f(x: type[Exception]):
e = None
try:
raise x
except ValueError as e:
pass
except:
pass
# error: [possibly-unresolved-reference]
reveal_type(e) # revealed: None