mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-16 01:25:11 +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>
2.3 KiB
2.3 KiB
del
statement
Basic
a = 1
del a
# error: [unresolved-reference]
reveal_type(a) # revealed: Unknown
# error: [invalid-syntax] "Invalid delete target"
del 1
# error: [unresolved-reference]
del a
x, y = 1, 2
del x, y
# error: [unresolved-reference]
reveal_type(x) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(y) # revealed: Unknown
def cond() -> bool:
return True
b = 1
if cond():
del b
# error: [possibly-unresolved-reference]
reveal_type(b) # revealed: Literal[1]
c = 1
if cond():
c = 2
else:
del c
# error: [possibly-unresolved-reference]
reveal_type(c) # revealed: Literal[2]
d = 1
def delete():
# TODO: this results in `UnboundLocalError`; we should emit `unresolved-reference`
del d
delete()
reveal_type(d) # revealed: Literal[1]
def delete_global():
global d
del d
delete_global()
# The variable should have been removed, but we won't check it for now.
reveal_type(d) # revealed: Literal[1]
Delete attributes
If an attribute is referenced after being deleted, it will be an error at runtime. But we don't
treat this as an error (because there may have been a redefinition by a method between the del
statement and the reference). However, deleting an attribute invalidates type narrowing by
assignment, and the attribute type will be the originally declared type.
Invalidate narrowing
class C:
x: int = 1
c = C()
del c.x
reveal_type(c.x) # revealed: int
# error: [unresolved-attribute]
del c.non_existent
c.x = 1
reveal_type(c.x) # revealed: Literal[1]
del c.x
reveal_type(c.x) # revealed: int
Delete an instance attribute definition
class C:
x: int = 1
c = C()
reveal_type(c.x) # revealed: int
del C.x
c = C()
# This attribute is unresolved, but we won't check it for now.
reveal_type(c.x) # revealed: int
Delete items
Deleting an item also invalidates the narrowing by the assignment, but accessing the item itself is still valid.
def f(l: list[int]):
del l[0]
# If the length of `l` was 1, this will be a runtime error,
# but if it was greater than that, it will not be an error.
reveal_type(l[0]) # revealed: int
# error: [call-non-callable]
del l["string"]
l[0] = 1
reveal_type(l[0]) # revealed: Literal[1]
del l[0]
reveal_type(l[0]) # revealed: int