ruff RUF051: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
1. If the key is unhashable, both the condition and the del/pop will raise TypeError at the same point, so no difference in error timing.
Program
Output
TypeError: unhashable type: 'list'
(no stdout)
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · 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.
Explanation Nemotron 3 Super unverified
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.)
Reproduce
- file
prog.py(the before program)- minimized program
- r2-p001.py
- evidence sheet
- r2-p001.md
- regression fixture
- r2-p001.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py ruff check --preview --isolated --select RUF051 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
RUF051 fix · RUF051 conditional deletion pattern · RUF051 list membership check delete · RUF051 replace del with pop
- related astral-sh/ruff/issues/20700 Same rule RUF051 but the defect is silently dropping an else branch, not changing behavior on unhashable keys.
Details
- program
- Unhashable key - identical error timing
- hazard
- 1. If the key is unhashable, both the condition and the del/pop will raise TypeError at the same point, so no difference in error timing.
- oracle
stdout before: TypeError: unhashable type: 'list' stdout after:- run
- 2026-10-17T21-46-58-522Z-boj8-ruff-RUF051
generated program (10 lines) and the fix's diff
--- orig.py 2026-10-17 17:47:13.630865416 -0400 +++ fixed.py 2026-10-17 17:47:13.800900155 -0400 @@ -1,10 +1,10 @@ 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}') try: - if lst in dct: del dct[lst] + dct.pop(lst, None) except Exception as e: print(f'{type(e).__name__}: {e}')
rule documentation given to the models
# if-key-in-dict-del (RUF051)
Derived from the **Ruff-specific rules** linter.
Fix is always available.
## What it does
Checks for `if key in dictionary: del dictionary[key]`.
## Why is this bad?
To remove a key-value pair from a dictionary, it's more concise to use `.pop(..., None)`.
## Example
```python
dictionary = {}
if key in dictionary:
del dictionary[key]
```
Use instead:
```python
dictionary = {}
dictionary.pop(key, None)
```
## Fix safety
This rule's fix is marked as safe, unless the if statement contains comments.