fixhunt

ruff RUF051: fix changes program behavior

fix-changes-behaviorlabelled safereal · highrelated
Findings / 2026-10-17T21-46-58-522Z-boj8-ruff-RUF051 / r2-p001ruff 0.17.110 → 6 lines2026-10-17 21:46 UTC

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

before
1lst = [1]
2dct = {}
3try:
4 if lst in dct: del dct[lst]
5except Exception as e:
6 print(f'{type(e).__name__}: {e}')
after
1lst = [1]
2dct = {}
3try:
4 dct.pop(lst, None)
5except Exception as e:
6 print(f'{type(e).__name__}: {e}')

Output

stdout beforeexit 0
TypeError: unhashable type: 'list'
stdout afterexit 0
(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
1lst = [1]
2dct = {}
3try:
4 if lst in dct: del dct[lst]
5except Exception as e:
6 print(f'{type(e).__name__}: {e}')
7try:
8 if lst in dct: del dct[lst]
9except Exception as e:
10 print(f'{type(e).__name__}: {e}')
--- 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.