fixhunt

ruff RUF019

labelled unknown
Runs / 2026-10-17T21-39-23-080Z-ah8u-ruff-RUF019ruff 0.17.12026-10-17 21:39 UTC8 programs × 2 round(s)16 programs, 12 fired, 12 fixed, 5 findings, 1 rejectedmodel cost $0.06

Analysis Nemotron 3 Super

The rule flags redundant 'in' checks before dict[key] when followed by truthiness test.

Fix: Replace 'if key in d and d[key]' with 'if d.get(key)' preserving original formatting and comments when safe.

11 hazards
  • Rewriting 'if "x" in d and d["x"]:' changes behavior when d["x"] evaluates to False but key exists, as d.get("x") returns the value (False) which is falsy, but the original condition would skip due to falsiness while the fix proceeds.
  • If d["x"] has side effects (e.g., a property getter), evaluating it twice in the original vs once in the fix alters side effect count.
  • When d["x"] raises KeyError for missing keys (impossible with 'in' guard), removing the guard could expose the error if the fix is applied incorrectly.
  • If d is not a dict but a custom mapping with __getitem__ side effects, the fix may not preserve behavior.
  • Comments between 'in' and 'and' or after 'd[key]' may be lost if the autofix does not preserve them.
  • If d[key] is used later in the same block, the fix does not eliminate redundant lookups; it only replaces the condition.
  • In nested expressions like f("x" in d and d["x"]), the fix may break if the surrounding context expects boolean short-circuiting.
  • When the value is 0, the original condition fails (due to falsiness) but d.get("x") returns 0 which is falsy, so behavior is preserved—but if the code relies on the key existence check separately, it is lost.
  • If d["x"] returns an object with custom __bool__ that returns False, the original skips the block but the fix enters it if the value is truthy after get—wait, no: both are falsy, so same outcome. Correction: hazard is when the value is truthy but the original condition fails due to other reasons? Actually, the original requires both key presence and truthy value. The fix only checks truthy value via get, which returns None if missing (falsy). So if key missing, original: short-circuits at 'in' → false; fix: get returns None → false. If key present and value falsy (0, [], etc): original: true and false → false; fix: get returns falsy → false. If key present and value truthy: both true. So logically equivalent. Hazard must be about side effects or non-dict mappings.
  • If d is a subclass of dict that overrides __contains__ or __getitem__ with side effects, the fix may alter behavior.
  • The fix assumes d.get(key) returns None for missing keys, but if default is overridden (not possible in get(key) call), it’s safe—however, if someone monkeypatched dict.get, it could break.

Findings

programoraclelabeljudgestatussummary
r1-p002fix-changes-behaviorlabelled safereal · highrelatedThe rule assumes the object is a dict-like with a .get method, but the program uses a custom class that implements __contains__ and __getitem__ without providi…
r1-p003fix-changes-behaviorlabelled safereal · highduplicateThe 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__…
r1-p007fix-changes-behaviorlabelled safereal · highrelatedThe rule assumes the target object has a `get` method like a standard dict, but the program uses a custom class `D` that implements `__contains__` and `__getit…
r2-p002fix-changes-behaviorlabelled safereal · highrelatedThe rule assumes the object has a `get` method like a standard dict, but the program uses a custom mapping class that implements `__contains__` and `__getitem_…
r2-p008fix-changes-behaviorlabelled safereal · highrelatedThe rule replaces an explicit key check (`'x' in d`) followed by access (`d['x']`) with a single `d.get('x')` call. In this program, the dictionary subclass ov…

Programs

idresulttitlehazard
r1-p001fix okKeyError exposed by incorrect removal of 'in' guardIf the fix removes the 'in' check without replacing with .get(), accessing d[key] for missing key raises KeyError.
r1-p002fix-changes-behaviorpathlib vs os.path: existence and symlink sensitivityPath.exists() and os.path.exists() differ on broken symlinks; the fix assumes dict-like truthiness but Path objects have different semantics.
r1-p003fix-changes-behaviorEvaluation order and side effects in merged expressionsOriginal evaluates 'in' then __getitem__; fix may evaluate __getitem__ twice or change order if not using .get().
r1-p004fix okCustom mapping with side effects in __getitem__If __getitem__ has side effects (e.g. state change), the original guards against it with 'in', but .get() still calls it.
r1-p005silent prediction offNested expression expecting boolean short-circuitingIn f("x" in d and d["x"]), the original short-circuits on missing key; fix using .get() may evaluate f's argument differently if .get() has side effects.
r1-p006fix okTruthiness: None vs falsy vs custom __bool__When d["x"] returns a custom object with __bool__ returning False, both versions agree—but if the fix mistakenly uses 'is not None', behavior diverges.
r1-p007fix-changes-behaviorExceptions inside rewritten code with finally/elseIf d["x"] raises an exception (e.g. ValueError), the original is guarded by 'in', but .get() does not suppress it—though for missing keys it returns None. Hazard arises if 'in' is removed and .get() n
r1-p008invalidCustom __bool__ returning False: equivalence under get()Both original and fix are logically equivalent when using .get(), but the hazard is clarifying that no divergence occurs here—this is a negative case to confirm understanding.
r2-p001fix okMonkey-patched dict.get altering fallback behavior1. The fix assumes d.get(key) returns None for missing keys, but if someone monkeypatched dict.get, it could break.
r2-p002fix-changes-behaviorCustom mapping with side effects in __getitem__2. If d is not a dict but a custom mapping with __getitem__ side effects, the fix may not preserve behavior.
r2-p003fix okExceptions in condition with finally clause3. exceptions raised inside the rewritten code, finally/else clauses, exception chaining
r2-p004fix okKey used later in block, redundant lookup not eliminated4. If d[key] is used later in the same block, the fix does not eliminate redundant lookups; it only replaces the condition.
r2-p005silent prediction offDescriptor with side effects on class attribute access5. class attributes vs instance attributes, descriptors, properties with side effects
r2-p006fix okValue is zero: falsy in both versions but existence check lost6. When the value is 0, the original condition fails (due to falsiness) but d.get("x") returns 0 which is falsy, so behavior is preserved—but if the code relies on the key existence check separately,
r2-p007silent prediction offStar-import and module-level side effect in condition7. star-imports, module-level side effects, __all__
r2-p008fix-changes-behaviorDict subclass overriding __contains__ and __getitem__ with side effects8. If d is a subclass of dict that overrides __contains__ or __getitem__ with side effects, the fix may alter behavior.

Sources

r1-p001 · KeyError exposed by incorrect removal of 'in' guard
1d = {}
2try:
3 if "x" in d and d["x"]:
4 print("inside")
5except KeyError as e:
6 print(f"KeyError: {e}")
7print("done")
--- orig.py	2026-10-17 17:39:36.405526324 -0400
+++ fixed.py	2026-10-17 17:39:36.579524841 -0400
@@ -1,6 +1,6 @@
 d = {}
 try:
-    if "x" in d and d["x"]:
+    if d.get("x"):
         print("inside")
 except KeyError as e:
     print(f"KeyError: {e}")
done
r1-p002 · pathlib vs os.path: existence and symlink sensitivity
1import os
2import tempfile
3with tempfile.TemporaryDirectory() as td:
4 p = os.path.join(td, 'link')
5 os.symlink('nonexistent', p)
6 class P:
7 def __contains__(self, k): return os.path.exists(p) if k == 'x' else False
8 def __getitem__(self, k):
9 if k == 'x': return p
10 raise KeyError(k)
11 d = P()
12 if "x" in d and d["x"]:
13 print("truthy")
14 else:
15 print("falsy")
--- orig.py	2026-10-17 17:39:36.693523869 -0400
+++ fixed.py	2026-10-17 17:39:36.911522010 -0400
@@ -9,7 +9,7 @@
             if k == 'x': return p
             raise KeyError(k)
     d = P()
-    if "x" in d and d["x"]:
+    if d.get("x"):
         print("truthy")
     else:
         print("falsy")
falsy
r1-p003 · Evaluation order and side effects in merged expressions
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)
true ['contains', 'getitem']
true after fix ['get', 'contains', 'getitem']
r1-p004 · Custom mapping with side effects in __getitem__
1call_log = []
2class M:
3 def __contains__(self, k): return k == 'x'
4 def __getitem__(self, k):
5 call_log.append(k)
6 if k == 'x': return 0
7 raise KeyError(k)
8 def get(self, k, default=None):
9 return self[k] if k in self else default
10
11m = M()
12if "x" in m and m["x"]:
13 print("block entered")
14else:
15 print("block skipped", call_log)
16call_log.clear()
17if m.get("x"):
18 print("block entered after fix")
19else:
20 print("block skipped after fix", call_log)
--- orig.py	2026-10-17 17:39:37.416517708 -0400
+++ fixed.py	2026-10-17 17:39:37.581516303 -0400
@@ -9,7 +9,7 @@
         return self[k] if k in self else default
 
 m = M()
-if "x" in m and m["x"]:
+if m.get("x"):
     print("block entered")
 else:
     print("block skipped", call_log)
block skipped ['x']
block skipped after fix ['x']
r1-p005 · Nested expression expecting boolean short-circuiting
1log = []
2class D:
3 def __contains__(self, k):
4 log.append(f'contains:{k}')
5 return k == 'x'
6 def __getitem__(self, k):
7 log.append(f'getitem:{k}')
8 return 1 if k == 'x' else None
9 def get(self, k, default=None):
10 log.append(f'get:{k}')
11 return self[k] if k in self else default
12
13def f(b):
14 return b
15
16d = D()
17result = f("x" in d and d["x"])
18print(f"original: {result}, log={log}")
19log.clear()
20result = f(d.get("x"))
21print(f"after fix: {result}, log={log}")
original: 1, log=['contains:x', 'getitem:x']
after fix: 1, log=['get:x', 'contains:x', 'getitem:x']
r1-p006 · Truthiness: None vs falsy vs custom __bool__
1class Falsy:
2 def __bool__(self): return False
3
4d = {'x': Falsy()}
5if "x" in d and d["x"]:
6 print("true")
7else:
8 print("false")
9if d.get("x"):
10 print("true after get")
11else:
12 print("false after get")
--- orig.py	2026-10-17 17:39:37.809514361 -0400
+++ fixed.py	2026-10-17 17:39:37.976512938 -0400
@@ -2,7 +2,7 @@
     def __bool__(self): return False
 
 d = {'x': Falsy()}
-if "x" in d and d["x"]:
+if d.get("x"):
     print("true")
 else:
     print("false")
false
false after get
r1-p007 · Exceptions inside rewritten code with finally/else
1class D:
2 def __contains__(self, k): return k == 'x'
3 def __getitem__(self, k):
4 if k == 'x': raise ValueError('bad')
5 return None
6 def get(self, k, default=None):
7 try:
8 return self[k]
9 except ValueError:
10 return default
11
12d = D()
13try:
14 if "x" in d and d["x"]:
15 print("yes")
16except ValueError as e:
17 print(f"caught: {e}")
18else:
19 print("no exception")
20try:
21 if d.get("x"):
22 print("yes after get")
23except ValueError as e:
24 print(f"caught after get: {e}")
25else:
26 print("no exception after get")
--- orig.py	2026-10-17 17:39:38.076512087 -0400
+++ fixed.py	2026-10-17 17:39:38.239510700 -0400
@@ -11,7 +11,7 @@
 
 d = D()
 try:
-    if "x" in d and d["x"]:
+    if d.get("x"):
         print("yes")
 except ValueError as e:
     print(f"caught: {e}")
caught: bad
no exception after get
r1-p008 · Custom __bool__ returning False: equivalence under get() (generated program is not deterministic)
1class Falsy:
2 def __bool__(self): return False
3
4d = {'x': Falsy()}
5in_check = "x" in d and d["x"]
6get_check = d.get("x")
7print(f'in_check: {in_check}, get_check: {bool(get_check)}')
8if in_check:
9 print("original block")
10else:
11 print("original skip")
12if get_check:
13 print("fix block")
14else:
15 print("fix skip")
r2-p001 · Monkey-patched dict.get altering fallback behavior
1d = {}
2original_get = dict.get
3def fake_get(self, key, default=None):
4 return default if key in self else 'patched'
5dict.get = fake_get
6try:
7 if 'x' in d and d['x']:
8 print('truthy')
9 else:
10 print(falsy if 'x' not in d else d['x'])
11except Exception as e:
12 print(f'{type(e).__name__}: {e}')
13finally:
14 dict.get = original_get
--- orig.py	2026-10-17 17:39:44.430458161 -0400
+++ fixed.py	2026-10-17 17:39:44.817454887 -0400
@@ -4,7 +4,7 @@
     return default if key in self else 'patched'
 dict.get = fake_get
 try:
-    if 'x' in d and d['x']:
+    if d.get('x'):
         print('truthy')
     else:
         print(falsy if 'x' not in d else d['x'])
r2-p002 · Custom mapping with side effects in __getitem__
1class SideEffectMap:
2 def __init__(self):
3 self.data = {'x': 1}
4 self.calls = 0
5 def __contains__(self, key):
6 return key in self.data
7 def __getitem__(self, key):
8 self.calls += 1
9 if key not in self.data:
10 raise KeyError(key)
11 return self.data[key]
12m = SideEffectMap()
13if 'x' in m and m['x']:
14 print(f'first: calls={m.calls}')
15if 'x' in m and m['x']:
16 print(f'second: calls={m.calls}')
17print(f'total calls: {m.calls}')
--- orig.py	2026-10-17 17:39:45.035453042 -0400
+++ fixed.py	2026-10-17 17:39:45.204451614 -0400
@@ -10,8 +10,8 @@
             raise KeyError(key)
         return self.data[key]
 m = SideEffectMap()
-if 'x' in m and m['x']:
+if m.get('x'):
     print(f'first: calls={m.calls}')
-if 'x' in m and m['x']:
+if m.get('x'):
     print(f'second: calls={m.calls}')
 print(f'total calls: {m.calls}')
first: calls=1
second: calls=2
total calls: 2
r2-p003 · Exceptions in condition with finally clause
1class BadDict(dict):
2 def __getitem__(self, key):
3 if key == 'x':
4 raise ValueError('bad')
5 return super().__getitem__(key)
6d = BadDict()
7try:
8 if 'x' in d and d['x']:
9 print('yes')
10except ValueError as e:
11 print(f'caught: {e}')
12finally:
13 print('finally')
--- orig.py	2026-10-17 17:39:45.414449839 -0400
+++ fixed.py	2026-10-17 17:39:45.576448469 -0400
@@ -5,7 +5,7 @@
         return super().__getitem__(key)
 d = BadDict()
 try:
-    if 'x' in d and d['x']:
+    if d.get('x'):
         print('yes')
 except ValueError as e:
     print(f'caught: {e}')
finally
r2-p004 · Key used later in block, redundant lookup not eliminated
1d = {'x': 42}
2access_count = 0
3class CountingDict(dict):
4 def __getitem__(self, key):
5 nonlocal access_count
6 access_count += 1
7 return super().__getitem__(key)
8d = CountingDict({'x': 42})
9if 'x' in d and d['x']:
10 val = d['x']
11 print(f'value: {val}, accesses: {access_count}')
--- orig.py	2026-10-17 17:39:45.679447599 -0400
+++ fixed.py	2026-10-17 17:39:46.087444151 -0400
@@ -6,6 +6,6 @@
         access_count += 1
         return super().__getitem__(key)
 d = CountingDict({'x': 42})
-if 'x' in d and d['x']:
+if d.get('x'):
     val = d['x']
     print(f'value: {val}, accesses: {access_count}')
r2-p005 · Descriptor with side effects on class attribute access
1class Desc:
2 def __get__(self, obj, objtype=None):
3 print('Desc.__get__ called')
4 return 1
5class Container:
6 x = Desc()
7c = Container()
8if 'x' in c.__dict__ and c.x:
9 print('in block')
10else:
11 print('not in block')
not in block
r2-p006 · Value is zero: falsy in both versions but existence check lost
1d = {'x': 0}
2if 'x' in d and d['x']:
3 print('exists and truthy')
4else:
5 print(f"'x' in d: {'x' in d}, d['x']: {d['x']}")
--- orig.py	2026-10-17 17:39:46.413441397 -0400
+++ fixed.py	2026-10-17 17:39:46.580439987 -0400
@@ -1,5 +1,5 @@
 d = {'x': 0}
-if 'x' in d and d['x']:
+if d.get('x'):
     print('exists and truthy')
 else:
     print(f"'x' in d: {'x' in d}, d['x']: {d['x']}")
'x' in d: True, d['x']: 0
r2-p007 · Star-import and module-level side effect in condition
1import sys
2module_dict = {'x': 1}
3sys.modules[__name__].dict = module_dict
4exec('''
5from dict import *
6if 'x' in dict and dict['x']:
7 print('starred access')
8''', globals())
r2-p008 · Dict subclass overriding __contains__ and __getitem__ with side effects
1class LoggingDict(dict):
2 def __contains__(self, key):
3 print(f'__contains__({key})')
4 return super().__contains__(key)
5 def __getitem__(self, key):
6 print(f'__getitem__({key})')
7 return super().__getitem__(key)
8d = LoggingDict({'x': 1})
9if 'x' in d and d['x']:
10 print('condition passed')
--- orig.py	2026-10-17 17:39:47.030436187 -0400
+++ fixed.py	2026-10-17 17:39:47.188434854 -0400
@@ -6,5 +6,5 @@
         print(f'__getitem__({key})')
         return super().__getitem__(key)
 d = LoggingDict({'x': 1})
-if 'x' in d and d['x']:
+if d.get('x'):
     print('condition passed')
__contains__(x)
__getitem__(x)
condition passed