fixhunt

ruff RUF019: fix changes program behavior

fix-changes-behaviorlabelled safereal · highduplicate
Findings / 2026-10-17T21-39-23-080Z-ah8u-ruff-RUF019 / r1-p003ruff 0.17.122 → 9 lines2026-10-17 21:39 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

Original evaluates 'in' then __getitem__; fix may evaluate __getitem__ twice or change order if not using .get().

Program

before
1state = []
2class D:
3 def __contains__(self, k):
4 return k == 'x'
5 def __getitem__(self, k):
6 return 1 if k == 'x' else None
7d = D()
8if "x" in d and d["x"]:
9 print("true", state)
after
1state = []
2class D:
3 def __contains__(self, k):
4 return k == 'x'
5 def __getitem__(self, k):
6 return 1 if k == 'x' else None
7d = D()
8if d.get("x"):
9 print("true", state)

Output

stdout beforeexit 0
true []
stdout afterexit 1
(no stdout)
AttributeError: 'D' object has no attribute 'get'
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The rule assumes the target object has a `get` method like a standard dict, but the program uses a custom class that implements `__contains__` and `__getitem__` without providing `get`. The rewrite replaces a working containment check and item access with a call to a missing method, causing an AttributeError at runtime.

Explanation Nemotron 3 Super unverified

The original condition checks membership via `__contains__` then accesses `d['x']` using `__getitem__`. The replacement uses `dict.get`, which requires a `get` method; class `D` lacks it, causing an `AttributeError`. Since `__contains__` returns `True` for `'x'` and `__getitem__` returns `1` (truthy), the original prints `"true []"`; the rewritten version fails before reaching that point. Thus, the fix alters behavior by assuming a dictionary interface not present in `D`.

Reproduce

file
prog.py (the before program)
minimized program
r1-p003.py
evidence sheet
r1-p003.md
regression fixture
r1-p003.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 RUF019 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

RUF019 fix · RUF019 dict get optimization · in operator redundant check · __contains__ method replacement

  • duplicate astral-sh/ruff/issues/14011 Same rule RUF019 and same defect class: the fix replaces 'key in obj and obj[key]' with 'obj.get(key)' on an object lacking .get(), changing behavior.
  • related astral-sh/ruff/issues/12953 Same rule RUF019 but different cause: f-string side effects in contains_effect vs missing .get() method on custom mapping.
  • related astral-sh/ruff/issues/12285 Same rule RUF019 but different construct: missing detection with 'not' operator vs incorrect rewrite on custom mapping without .get().

Details

program
Evaluation order and side effects in merged expressions
hazard
Original evaluates 'in' then __getitem__; fix may evaluate __getitem__ twice or change order if not using .get().
oracle
stdout before: true [] stdout after: exit 0 → 1 stderr before: (none) stderr after: AttributeError: 'D' object has no attribute 'get'
run
2026-10-17T21-39-23-080Z-ah8u-ruff-RUF019
generated program (22 lines) and the fix's diff
1state = []
2class D:
3 def __contains__(self, k):
4 state.append('contains')
5 return k == 'x'
6 def __getitem__(self, k):
7 state.append('getitem')
8 return 1 if k == 'x' else None
9 def get(self, k, default=None):
10 state.append('get')
11 return self[k] if k in self else default
12
13d = D()
14if "x" in d and d["x"]:
15 print("true", state)
16else:
17 print("false", state)
18state.clear()
19if d.get("x"):
20 print("true after fix", state)
21else:
22 print("false after fix", state)
--- orig.py	2026-10-17 17:39:37.155519931 -0400
+++ fixed.py	2026-10-17 17:39:37.312518594 -0400
@@ -11,7 +11,7 @@
         return self[k] if k in self else default
 
 d = D()
-if "x" in d and d["x"]:
+if d.get("x"):
     print("true", state)
 else:
     print("false", state)
rule documentation given to the models
# unnecessary-key-check (RUF019)

Derived from the **Ruff-specific rules** linter.

Fix is always available.

## What it does
Checks for unnecessary key checks prior to accessing a dictionary.

## Why is this bad?
When working with dictionaries, the `get` can be used to access a value
without having to check if the dictionary contains the relevant key,
returning `None` if the key is not present.

## Example
```python
if "key" in dct and dct["key"]:
    ...
```

Use instead:
```python
if dct.get("key"):
    ...
```

## Fix safety
This rule's fix is marked as safe, unless the expression contains comments
or may have side effects.