ruff PLR1736
labelled unknown
Analysis Nemotron 3 Super
Flags redundant indexing of a list when using enumerate where the element is already provided by the loop variables.
Fix: Replace the indexed list access with the loop variable that corresponds to the current element from enumerate.
10 hazards
- Rewriting `lst[i]` to `x` changes behavior if `lst` is reassigned within the loop body, as the original code uses the updated list while the fix uses the stale loop variable.
- If `lst` is a property with a setter, accessing `lst[i]` may trigger the setter on each iteration, whereas using `x` avoids this entirely.
- When `lst` is a mutable local variable that is later rebound in an outer scope, the fix captures the value at loop entry, while the original reflects rebinds.
- If the list element is a class instance with a custom `__getitem__` that has side effects, replacing `lst[i]` with `x` omits those side effects.
- In cases where `lst` is a multidimensional list and the index is a tuple (e.g., `lst[i][j]`), the fix cannot be applied because the loop variables do not match the indexing structure.
- If the loop variable is named identically to the list (e.g., `for lst, lst in enumerate(lst): lst[lst]`), the fix risks incorrect variable capture due to name shadowing.
- When the indexed access appears in an annotated assignment (e.g., `lst[i]: int = 5`), removing the index breaks the annotation's target.
- If the indexed expression is part of a larger subscript (e.g., `lst[i][0]`), the fix would incorrectly replace only the outer index, leaving an invalid access.
- Comments attached to the indexed expression (e.g., `lst[i] # comment`) may be lost if the autofix does not preserve trailing comments during replacement.
- In a `walrus` expression like `if (tmp := lst[i]) > 0:`, replacing `lst[i]` with `x` alters the assignment target and may affect subsequent uses of `tmp`.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p002 | fix-changes-behavior | labelled safe | real · high | duplicate | The fix replaces `lst[i]` with the loop variable `x`, assuming the list is not mutated during iteration. However, the program rebinds `lst` to a new list `[99,… |
| r2-p004 | fix-changes-behavior | labelled safe | real · high | duplicate | The rule assumes the enumerate target is a list and the loop variables are distinct (index, item), so it replaces lst[lst] with lst. In the program the same na… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Keyword-only, positional-only, *args/**kwargs forwarding | Rewriting enumerate index use breaks argument forwarding where positional/keyword semantics differ based on evaluation timing. |
| r1-p002 | fix-changes-behavior | Mutable local rebound in outer scope | Fix captures list value at loop start; original uses rebound value, causing divergence when list is reassigned in outer scope during iteration. |
| r1-p003 | invalid | Mutable default arguments and aliasing | Rewriting to loop variable avoids re-evaluation of mutable default, but original re-indexes mutated default; aliasing causes shared state exposure. |
| r1-p004 | silent prediction off | Dict ordering, duplicate keys, __hash__/__eq__ overrides | Rewriting to loop variable avoids re-hashing/re-looking-up dict key; original re-computes hash and may hit different entry if __eq__ is unstable. |
| r1-p005 | invalid | Bytes vs str, encoding and newline handling | Rewriting to loop variable avoids re-encoding/decoding; original re-indexes bytes/str and may decode differently if mutating or slicing encoding-sensitive content. |
| r1-p006 | fix ok | String formatting edge cases: %, format, f-strings, repr vs str | Rewriting to loop variable avoids re-evaluating __str__/__repr__; original re-indexes and may call __str__ again with side effects. |
| r1-p007 | fix ok | Class vs instance attributes, descriptors, properties with side effects | Rewriting to loop variable avoids re-triggering __get__; original re-indexes and may invoke descriptor/__getattribute__ again with state change. |
| r1-p008 | fix ok | Truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' | Rewriting to loop variable avoids re-evaluating __bool__/__len__; original re-indexes and may invoke __bool__ again, changing control flow. |
| r2-p001 | fix ok | Walrus assignment changes target | In a walrus expression like `if (tmp := lst[i]) > 0:`, replacing `lst[i]` with `x` alters the assignment target and may affect subsequent uses of `tmp`. |
| r2-p002 | silent prediction off | Class vs instance attributes, descriptors, properties with side effects | Rewriting to loop variable avoids re-triggering __get__; original re-indexes and triggers descriptor each time. |
| r2-p003 | silent prediction off | Property setter triggered on each list access | If `lst` is a property with a setter, accessing `lst[i]` may trigger the setter on each iteration, whereas using `x` avoids this entirely. |
| r2-p004 | fix-changes-behavior | Variable capture due to name shadowing in enumerate | If the loop variable is named identically to the list (e.g., `for lst, lst in enumerate(lst): lst[lst]`), the fix risks incorrect variable capture due to name shadowing. |
| r2-p005 | fix ok | Exceptions in rewritten code with finally/else | Exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p006 | silent prediction off | Dict ordering, duplicate keys, __hash__/__eq__ overrides | Rewriting to loop variable avoids re-hashing/re-looking-up dict key; original re-indexes and may re-hash. |
| r2-p007 | invalid | Custom __getitem__ with side effects | If the list element is a class instance with a custom `__getitem__` that has side effects, replacing `lst[i]` with `x` omits those side effects. |
| r2-p008 | fix ok | String formatting edge cases: % , format, f-strings, repr vs str | Rewriting to loop variable avoids re-evaluating __str__/__repr__; original re-indexes and may trigger __str__ again. |
Sources
r1-p001 · Keyword-only, positional-only, *args/**kwargs forwarding
1def f(a, /, b, *, c=print, d=None):
2 c(f'a={a}, b={b}, c={c.__name__ if callable(c) else c}, d={d}')
3
4lst = [1, 2]
5def wrapper(*args, **kwargs):
6 return f(*args, **kwargs)
7
8for i, x in enumerate(lst):
9 # Rule triggers on lst[i] -> should become x
10 wrapper(lst[i], b=x, c=print, d=lst[i])
--- orig.py 2026-10-17 17:04:46.764867461 -0400 +++ fixed.py 2026-10-17 17:04:46.929869320 -0400 @@ -7,4 +7,4 @@ for i, x in enumerate(lst): # Rule triggers on lst[i] -> should become x - wrapper(lst[i], b=x, c=print, d=lst[i]) + wrapper(x, b=x, c=print, d=x)
a=1, b=1, c=print, d=1 a=2, b=2, c=print, d=2
r1-p002 · Mutable local rebound in outer scope
1lst = [10, 20]
2for i, x in enumerate(lst):
3 print(f'Before: lst[{i}]={lst[i]}, x={x}')
4 if i == 0:
5 lst = [99, 88] # Rebinding in outer scope
6 print(f'After: lst[{i}]={lst[i]}, x={x}')
--- orig.py 2026-10-17 17:04:47.037870536 -0400 +++ fixed.py 2026-10-17 17:04:47.205872429 -0400 @@ -1,6 +1,6 @@ lst = [10, 20] for i, x in enumerate(lst): - print(f'Before: lst[{i}]={lst[i]}, x={x}') + print(f'Before: lst[{i}]={x}, x={x}') if i == 0: lst = [99, 88] # Rebinding in outer scope print(f'After: lst[{i}]={lst[i]}, x={x}')
Before: lst[0]=10, x=10 After: lst[0]=99, x=10 Before: lst[1]=88, x=20 After: lst[1]=88, x=20
r1-p003 · Mutable default arguments and aliasing (generated program does not terminate within the time limit)
1def g(x, lst=[]):
2 lst.append(x)
3 return lst
4
5data = [1, 2]
6results = []
7for idx, val in enumerate(data):
8 # Rule triggers on data[idx] -> should become val
9 results.append(g(val, data)) # Passing data as explicit lst
10print('Results:', results)
11print('Final data:', data)
r1-p004 · Dict ordering, duplicate keys, __hash__/__eq__ overrides
1class BadKey:
2 def __init__(self, val): self.val = val
3 def __hash__(self): return hash(self.val)
4 def __eq__(self, other):
5 # Simulate hash collision drift
6 return self.val == other.val and hasattr(self, '_eq_count') and self._eq_count < 2
7
8d = {}
9k1 = BadKey(1)
10k2 = BadKey(1) # Same hash, potentially eq
11d[k1] = 'first'
12d[k2] = 'second' # Overwrites if __eq__ says equal
13
14items = list(d.items())
15for pos, (k, v) in enumerate(items):
16 # Rule triggers on d[k] -> should become v
17 print(f'd[{k.val}] via index: {d[k]}, via loop: {v}')
d[1] via index: first, via loop: first d[1] via index: second, via loop: second
r1-p005 · Bytes vs str, encoding and newline handling (generated program does not parse: Traceback (most recent call last):)
1data = b'h\xe9llo
2w\xf6rld' # UTF-8 bytes: hèllo
3wörd
4for ix, b in enumerate(data):
5 # Rule triggers on data[ix] -> should become b
6 char = chr(b) if b < 128 else '?'
7 reconstructed = bytes([data[ix]]) # Original: re-index
8 print(f'idx={ix}: byte={b} ({char}), reconstructed={reconstructed!r}')
r1-p006 · String formatting edge cases: %, format, f-strings, repr vs str
1class Show:
2 def __init__(self, n): self.n = n
3 def __str__(self):
4 print(f'__str__ called for {self.n}')
5 return str(self.n)
6 __repr__ = __str__
7
8items = [Show(1), Show(2)]
9for i, x in enumerate(items):
10 # Rule triggers on items[i] -> should become x
11 s = f'{items[i]}:{x}'
12 print(s)
--- orig.py 2026-10-17 17:04:52.700934264 -0400 +++ fixed.py 2026-10-17 17:04:52.865936120 -0400 @@ -8,5 +8,5 @@ items = [Show(1), Show(2)] for i, x in enumerate(items): # Rule triggers on items[i] -> should become x - s = f'{items[i]}:{x}' + s = f'{x}:{x}' print(s)
__str__ called for 1 __str__ called for 1 1:1 __str__ called for 2 __str__ called for 2 2:2
r1-p007 · Class vs instance attributes, descriptors, properties with side effects
1class Desc:
2 def __init__(self, val): self.val = val
3 def __get__(self, obj, objtype):
4 print(f'Desc.__get__ called with val={self.val}')
5 return self.val
6
7class Container:
8 attr = Desc(42)
9 def __init__(self):
10 self.instance_val = 99
11
12c = Container()
13lst = [c, c]
14for i, obj in enumerate(lst):
15 # Rule triggers on lst[i].attr -> should become obj.attr
16 print(f'Access {i}:', end=' ')
17 v = lst[i].attr # Triggers __get__
--- orig.py 2026-10-17 17:04:52.962937210 -0400 +++ fixed.py 2026-10-17 17:04:53.127939065 -0400 @@ -14,4 +14,4 @@ for i, obj in enumerate(lst): # Rule triggers on lst[i].attr -> should become obj.attr print(f'Access {i}:', end=' ') - v = lst[i].attr # Triggers __get__ + v = obj.attr # Triggers __get__
Access 0: Desc.__get__ called with val=42 Access 1: Desc.__get__ called with val=42
r1-p008 · Truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='
1class Truthy:
2 def __init__(self, flag): self.flag = flag
3 def __bool__(self):
4 print(f'__bool__ called, returning {self.flag}')
5 return self.flag
6
7data = [Truthy(True), Truthy(False)]
8for n, v in enumerate(data):
9 # Rule triggers on data[n] if v > 0 else 0 -> but v is Truthy, not int! Adjusting to match pattern: use v in bool context
10 # Instead: data[n] is used in conditional
11 if data[n]: # Rule should trigger here? No — rule only triggers on direct indexing like lst[i], not in if
12 # So we need to match the hazard pattern: for n, v in enumerate(nums): nums[n] if v > 0 else 0
13 # Let's change to: nums[n] is used in expression
14 pass
15# Redo to match exact pattern: for n, v in enumerate(nums): nums[n] if v > 0 else 0
16# But v is Truthy, not comparable to int. So make v int, but data[n] has side effect
17class NumWithBool:
18 def __init__(self, val): self.val = val
19 def __bool__(self):
20 print(f'__bool__({self.val})')
21 return self.val != 0
22 def __int__(self): return self.val
23
24nums = [NumWithBool(1), NumWithBool(0)]
25for n, v in enumerate(nums):
26 # Rule triggers on nums[n] -> should become v
27 result = nums[n] if v > 0 else 0 # Here, nums[n] is indexed, v is loop var
28 print(f'n={n}, v={v}, result={result}')
--- orig.py 2026-10-17 17:04:53.232940245 -0400 +++ fixed.py 2026-10-17 17:04:53.623944640 -0400 @@ -8,7 +8,7 @@ for n, v in enumerate(data): # Rule triggers on data[n] if v > 0 else 0 -> but v is Truthy, not int! Adjusting to match pattern: use v in bool context # Instead: data[n] is used in conditional - if data[n]: # Rule should trigger here? No — rule only triggers on direct indexing like lst[i], not in if + if v: # Rule should trigger here? No — rule only triggers on direct indexing like lst[i], not in if # So we need to match the hazard pattern: for n, v in enumerate(nums): nums[n] if v > 0 else 0 # Let's change to: nums[n] is used in expression pass @@ -24,5 +24,5 @@ nums = [NumWithBool(1), NumWithBool(0)] for n, v in enumerate(nums): # Rule triggers on nums[n] -> should become v - result = nums[n] if v > 0 else 0 # Here, nums[n] is indexed, v is loop var + result = v if v > 0 else 0 # Here, nums[n] is indexed, v is loop var print(f'n={n}, v={v}, result={result}')
__bool__ called, returning True __bool__ called, returning False
r2-p001 · Walrus assignment changes target
1lst = [1, 2, 3]
2for i, x in enumerate(lst):
3 if (tmp := lst[i]) > 0:
4 tmp *= 2
5 else:
6 tmp = 0
7 print(f'i={i}, tmp={tmp}', end=' ')
8print()
--- orig.py 2026-10-17 17:05:01.181029480 -0400 +++ fixed.py 2026-10-17 17:05:01.360031487 -0400 @@ -1,6 +1,6 @@ lst = [1, 2, 3] for i, x in enumerate(lst): - if (tmp := lst[i]) > 0: + if (tmp := x) > 0: tmp *= 2 else: tmp = 0
i=0, tmp=2 i=1, tmp=4 i=2, tmp=6
r2-p002 · Class vs instance attributes, descriptors, properties with side effects
1class Desc:
2 def __get__(self, obj, objtype):
3 print('Desc.__get__')
4 return [10, 20, 30]
5
6class Container:
7 attr = Desc()
8
9c = Container()
10for i, x in enumerate(c.attr):
11 print(f'idx={i}, val={x}, c.attr[{i}]={c.attr[i]}', end=' ')
12print()
Desc.__get__ Desc.__get__ idx=0, val=10, c.attr[0]=10 Desc.__get__ idx=1, val=20, c.attr[1]=20 Desc.__get__ idx=2, val=30, c.attr[2]=30
r2-p003 · Property setter triggered on each list access
1class Bag:
2 def __init__(self):
3 self._data = [5, 6, 7]
4 self.count = 0
5 @property
6 def data(self):
7 return self._data
8 @data.setter
9 def data(self, v):
10 self.count += 1
11 self._data = v
12
13b = Bag()
14for i, x in enumerate(b.data):
15 b.data[i] = x + 1 # triggers setter each time!
16print(f'setter called {b.count} times')
setter called 0 times
r2-p004 · Variable capture due to name shadowing in enumerate
1lst = ['a', 'b', 'c']
2for lst, lst in enumerate(lst): # outer lst shadows inner? actually, first is index, second is element
3 # Rule sees: lst[lst] -> wants to replace with lst (element), but lst is now element, not list
4 # So original: lst[index] -> element at index
5 # Fix: lst (element) -> which is a string, not indexable -> TypeError
6 try:
7 result = lst[lst] # original: indexes the list with the element (string) -> error
8 except Exception as e:
9 print(f'{type(e).__name__}: {e}', end=' ')
10print()
--- orig.py 2026-10-17 17:05:01.732035658 -0400 +++ fixed.py 2026-10-17 17:05:01.900037542 -0400 @@ -4,7 +4,7 @@ # So original: lst[index] -> element at index # Fix: lst (element) -> which is a string, not indexable -> TypeError try: - result = lst[lst] # original: indexes the list with the element (string) -> error + result = lst # original: indexes the list with the element (string) -> error except Exception as e: print(f'{type(e).__name__}: {e}', end=' ') print()
TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str'
r2-p005 · Exceptions in rewritten code with finally/else
1lst = [1, 2, 0]
2for i, x in enumerate(lst):
3 try:
4 y = 10 / lst[i] # rule fires: lst[i] -> x
5 except ZeroDivisionError as e:
6 print(f'DivByZero at i={i}', end=' ')
7 else:
8 print(f'OK: {y}', end=' ')
9 finally:
10 print('finally', end=' ')
11print()
--- orig.py 2026-10-17 17:05:01.998038641 -0400 +++ fixed.py 2026-10-17 17:05:02.161040467 -0400 @@ -1,7 +1,7 @@ lst = [1, 2, 0] for i, x in enumerate(lst): try: - y = 10 / lst[i] # rule fires: lst[i] -> x + y = 10 / x # rule fires: lst[i] -> x except ZeroDivisionError as e: print(f'DivByZero at i={i}', end=' ') else:
OK: 10.0 finally OK: 5.0 finally DivByZero at i=2 finally
r2-p006 · Dict ordering, duplicate keys, __hash__/__eq__ overrides
1class BadKey:
2 def __init__(self, val):
3 self.val = val
4 def __hash__(self):
5 print(f'hash({self.val})')
6 return hash(self.val)
7 def __eq__(self, other):
8 return isinstance(other, BadKey) and self.val == other.val
9
10keys = [BadKey(1), BadKey(2), BadKey(1)] # note: duplicate logical key
11d = {k: k.val*10 for k in keys}
12for i, k in enumerate(keys):
13 _ = d[k] # original: re-hashes k each time
14print('done')
hash(1) hash(2) hash(1) hash(1) hash(2) hash(1) done
r2-p007 · Custom __getitem__ with side effects (generated program does not parse: Traceback (most recent call last):)
1class Wrapper:
2 def __init__(self, value):
3 self.value = value
4 self.access_log = []
5 def __getitem__(self, key):
6 self.access_log.append(key)
7 return self.value + key
8
9w = Wrapper(7)
10 lst = [w] * 3 # three references to same wrapper
11for i, x in enumerate(lst):
12 val = lst[i][0] # rule fires: lst[i] -> x, then x[0]; but fix would lose __getitem__ log
13print(f'access log: {w.access_log}')
r2-p008 · String formatting edge cases: % , format, f-strings, repr vs str
1class Foo:
2 def __init__(self, n):
3 self.n = n
4 def __str__(self):
5 print(f'str({self.n})')
6 return f'str_{self.n}'
7 def __repr__(self):
8 print(f'repr({self.n})')
9 return f'repr_{self.n}'
10
11lst = [Foo(1), Foo(2)]
12for i, x in enumerate(lst):
13 s = f'{lst[i]}' # rule fires: lst[i] -> x
14print('line')
15for i, x in enumerate(lst):
16 s = str(lst[i]) # rule fires again
17print('done')
--- orig.py 2026-10-17 17:05:02.538044693 -0400 +++ fixed.py 2026-10-17 17:05:02.705046565 -0400 @@ -10,8 +10,8 @@ lst = [Foo(1), Foo(2)] for i, x in enumerate(lst): - s = f'{lst[i]}' # rule fires: lst[i] -> x + s = f'{x}' # rule fires: lst[i] -> x print('line') for i, x in enumerate(lst): - s = str(lst[i]) # rule fires again + s = str(x) # rule fires again print('done')
str(1) str(2) line str(1) str(2) done