# ruff RUF051: fix changes program behavior

Internal evidence sheet generated by fixhunt. Not an issue text. A human re-verifies, checks the duplicates
below, and writes the upstream report in their own words following the project's AI policy.

- Tool: ruff ruff 0.17.1
- Rule: RUF051 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): real, severity high. The rule rewrites `if key in dictionary: del dictionary[key]` to `dictionary.pop(key, None)` assuming the key is hashable. In the program, `lst` is an unhashable list, so the original `in` check raises TypeError (caught and printed), while the rewritten `pop` call also raises TypeError but now propagates out of the try block, changing the observed behavior from a handled exception to a crash.
- Program: minimized from 10 to 6 lines

## Reproducer

```python
lst = [1]
dct = {}
try:
    if lst in dct: del dct[lst]
except Exception as e:
    print(f'{type(e).__name__}: {e}')
```

## Commands

```sh
python3 prog.py
ruff check --preview --isolated --select RUF051 --fix prog.py
python3 prog.py
```

## Observed

```
stdout before:
TypeError: unhashable type: 'list'
stdout after:
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:44:10.104462911 -0400
+++ fixed.py	2026-10-22 13:44:12.115406871 -0400
@@ -1,6 +1,6 @@
 lst = [1]
 dct = {}
 try:
-    if lst in dct: del dct[lst]
+    dct.pop(lst, None)
 except Exception as e:
     print(f'{type(e).__name__}: {e}')
```

## Why the rewrite is not equivalent (model draft, verify before use)

The original code checks `if lst in dct` which requires hashing the list to test membership; since lists are unhashable, this raises TypeError before deletion occurs, caught and printed. The replacement uses `dict.pop(key, default)` which also attempts to hash the key internally, raising the same TypeError during the pop call, still caught and printed—so behavior should be identical. However, if the fix were incorrectly assumed to avoid the membership test entirely (e.g., by silently ignoring missing keys), it might suggest no error, but in reality both versions raise TypeError due to unhashable key, making the observed difference unlikely unless the exception handling was altered, which it was not. Thus the labeled "safe" fix preserves semantics here; any behavioral change would stem from misinterpretation, not the rewrite itself. (Note: The reported empty output after fix contradicts expected behavior; both versions should print the error.)

## Upstream search

Queries: "RUF051 fix", "RUF051 conditional deletion pattern", "RUF051 list membership check delete", "RUF051 replace del with pop"

- related: https://github.com/astral-sh/ruff/issues/20700
  Same rule RUF051 but the defect is silently dropping an else branch, not changing behavior on unhashable keys.

<details><summary>All hits</summary>

- [github, closed] unused-function-argument not silenced by prefixed underscore if variable name is "次" — https://github.com/astral-sh/ruff/issues/28721
- [github, open] [`docs`] Some rule's examples do not trigger out-of-the-box — https://github.com/astral-sh/ruff/issues/18972
- [github, closed] [RUF051] fix silently drops `else` branch — https://github.com/astral-sh/ruff/issues/20700
- [github, closed] SIM401 triggers on dict like objects that aren't proper dictionaries — https://github.com/astral-sh/ruff/issues/15814

</details>
