fixhunt

ruff PLE4703

labelled unknown
Runs / 2026-10-17T20-59-08-073Z-9q1i-ruff-PLE4703ruff 0.17.12026-10-17 20:59 UTC8 programs × 2 round(s)16 programs, 8 fired, 0 fixed, 0 findings, 2 rejectedmodel cost $0.0056

Analysis Nemotron 3 Super

Flags loops where a set is directly modified via methods like add(), remove(), discard(), pop(), clear(), or update() while iterating over that same set.

Fix: Rewrites the for-loop header from 'for var in set_var:' to 'for var in set_var.copy():', leaving the loop body unchanged.

8 hazards
  • The rewrite evaluates set_var.copy() at loop start, so if set_var is reassigned before the loop begins, the original code would iterate over the reassigned set while the fix iterates over a copy of the original set, changing which elements are processed.
  • If set_var is a property or item access (e.g., obj.attr or arr[i]), the fix evaluates it once for the copy, whereas the original re-evaluates it on each iteration, potentially iterating over a different object if the property/item changes during looping.
  • When the set variable is aliased (another name refers to the same set), modifications via the alias in the loop body affect the original set, but the fix iterates over a snapshot, so the loop may terminate earlier or later than in the original code.
  • If the loop body contains a break, continue, or return statement, the fix may execute fewer iterations than the original would have before the error, altering side effects that depend on the number of iterations.
  • Any expressions in the loop header beyond the set variable (e.g., in a nested loop or complex target list) are not adjusted by the fix, so malformed headers could become invalid or change meaning after rewriting.
  • The fix does not preserve trailing comments on the for line; they are dropped because the entire header is replaced.
  • If the set variable is used in a comprehension or lambda within the loop body that captures the variable by reference, the fix’s use of a copy may cause those inner functions to see a different set than the original code would have.
  • When the set is empty, the fix still evaluates .copy() (producing an empty set), which is behaviorally equivalent, but if .copy() is overridden in a subclass to have side effects, those side effects occur where none existed before.

Programs

idresulttitlehazard
r1-p001silent prediction offReassignment Before LoopIf `set_var` is reassigned before the loop starts, evaluating `.copy()` too early causes iteration over stale data.
r1-p002fired, no fixNon-file Context ManagerUsing .copy() inside a non-file cm still locks snapshot; mutation proceeds fine but semantics differ if __exit__ depends on live state.
r1-p003fired, no fixComplex Loop HeaderNested loop variables or destructuring targets are not copied, breaking semantics when mutated.
r1-p004fired, no fixLambda Capture in Body.copy() freezes the set; lambdas inside body capture the copy, not the mutating original.
r1-p005silentText Encoding Edge CaseWriting strings vs bytes differs; .copy() doesn't affect I/O but deterministic trace requires showing state split across modes.
r1-p006fired, no fix prediction offGenerator ConsumptionIterating a generator once exhausts it; .copy() creates eager snapshot but underlying mutation still affects later reuse if aliased.
r1-p007invalidString Formatting%s, .format(), f-strings all resolve values at runtime; copying the iterated set doesn't change formatting but proves determinism via captured values.
r1-p008fired, no fixMutable Default AliasingLoop variable aliased as default arg; .copy() breaks identity causing default to bind to stale copy.
r2-p001fired, no fixbytes vs str encodingtext/binary mode confusion affecting set mutation safety during iteration
r2-p002invalidtype annotations runtime evaltyping-only import causing NameError at runtime when annotation is evaluated
r2-p003fired, no fixnon-file context managerobject with write() but no close() used in with statement
r2-p004fired, no fixexceptions in rewritten codeexception inside loop body triggering finally/else interaction
r2-p005silent prediction offcustom truthiness__bool__ and __len__ affecting loop entry and set mutation safety
r2-p006silent prediction offreassignment before loopset_var reassigned before loop, so .copy() uses old value but loop iterates over new set
r2-p007silent prediction offasync def await orderingasync iteration over set with mutation inside awaited coroutine
r2-p008silent prediction offevaluation order side effectsset var appears in multiple places, copy() evaluates once but original may change

Sources

r1-p001 · Reassignment Before Loop
1S = {1}
2S = {2}
3print('start:', S)
4for x in S:
5 S.add(99)
6 break
7print('end:', sorted(S))
start: {2}
end: [2, 99]
r1-p002 · Non-file Context Manager
1class CM:
2 def __enter__(self): return self
3 def __exit__(self, *exc):
4 if hasattr(self, 'seen') and 99 in self.seen:
5 raise ValueError('99 seen')
6with CM() as cm:
7 s = {1, 2}
8 cm.seen = set()
9 for x in s:
10 cm.seen.add(x)
11 s.add(99)
12print('ok')
r1-p003 · Complex Loop Header
1s = {(1, 2), (3, 4)}
2for (a, b) in s:
3 s.clear()
4 print(a + b)
5break
r1-p004 · Lambda Capture in Body
1s = {1}
2fns = []
3for x in s:
4 fns.append(lambda: x in s)
5 s.add(2)
6print([f() for f in fns])
r1-p005 · Text Encoding Edge Case
1data = {'α', 'β'}
2open('tmp.txt', 'w', encoding='utf-8').write(''.join(sorted(data)))
3with open('tmp.txt', 'r', encoding='utf-8') as f:
4 content = f.read()
5import os; os.remove('tmp.txt')
6print(repr(content))
'αβ'
r1-p006 · Generator Consumption
1def gen():
2 yield from {1, 2}
3g = gen()
4s = set(g)
5for x in s:
6 s.discard(x)
7print(len(s))
r1-p007 · String Formatting (generated program does not parse: Traceback (most recent call last):)
1s = {1}
2for x in s:
3 s.add(2)
4 print(f'{x}:{%d}' % len(s))
5break
r1-p008 · Mutable Default Aliasing
1s = {1}
2def f(x, seen=s):
3 seen.add(99)
4 return len(seen)
5for x in s:
6 print(f(x))
7 s.clear()
8break
r2-p001 · bytes vs str encoding
1data = b'hello world'
2s = {ord(c) for c in data.decode()}
3for x in s:
4 try:
5 s.add(len(data))
6 except RuntimeError as e:
7 print('RuntimeError:', type(e).__name__, str(e))
8print('final s:', sorted(s))
r2-p002 · type annotations runtime eval (generated program does not terminate within the time limit)
1from typing import List
2s: List[int] = [1, 2]
3try:
4 for x in s:
5 s.append(3)
6except NameError as e:
7 print('NameError:', type(e).__name__, str(e))
8print('s:', s)
r2-p003 · non-file context manager
1class Buffer:
2 def __init__(self):
3 self.data = []
4 def write(self, x):
5 self.data.append(x)
6buf = Buffer()
7s = {1, 2}
8with buf:
9 for x in s:
10 buf.write(x)
11 s.add(3)
12print('buffer:', buf.data)
13print('set:', s)
r2-p004 · exceptions in rewritten code
1s = {1, 2}
2try:
3 for x in s:
4 if x == 1:
5 raise ValueError('bad')
6 s.discard(x)
7except ValueError as e:
8 print('ValueError:', type(e).__name__, str(e))
9else:
10 print('no exception')
11finally:
12 print('final set:', s)
ValueError: ValueError bad
final set: {1, 2}
r2-p005 · custom truthiness
1class SetLike:
2 def __init__(self):
3 self._data = {1, 2}
4 def __iter__(self):
5 return iter(self._data)
6 def __bool__(self):
7 return len(self._data) > 0
8 def __len__(self):
9 return len(self._data)
10 def discard(self, x):
11 self._data.discard(x)
12s = SetLike()
13for x in s:
14 s.discard(x)
15print('remaining:', list(s._data))
r2-p006 · reassignment before loop
1s = {1, 2}
2s = {3, 4}
3for x in s:
4 print('x:', x)
5 s.add(5)
6print('final s:', sorted(s))
x: 3
r2-p007 · async def await ordering
1async def worker(s):
2 for x in s:
3 await asyncio.sleep(0)
4 s.add(x + 10)
5async def main():
6 s = {1, 2}
7 await worker(s)
8 print('result:', sorted(s))
9asyncio.run(main())
r2-p008 · evaluation order side effects
1def get_set():
2 print('get_set called')
3 return {1, 2}
4s = get_set()
5for x in s:
6 print('x:', x)
7 s.clear()
8print('final s:', s)
get_set called
x: 1