fixhunt

ruff C400

labelled unknown
Runs / 2026-10-17T20-16-19-854Z-8jsy-ruff-C400ruff 0.17.12026-10-17 20:16 UTC8 programs × 2 round(s)16 programs, 12 fired, 0 fixed, 0 findings, 1 rejectedmodel cost $0.0033

Analysis Nemotron 3 Super

Flags unnecessary uses of list() wrapping generator expressions.

Fix: Rewrites list(<generator expression>) to [<expression> for <iteration>] when the generator has a transformation or filter; otherwise rewrites to list(<iterable>) by removing the redundant generator wrapper.

11 hazards
  • If the generator expression contains a walrus operator (e.g., list((y := f(x)) for x in foo)), rewriting to a list comprehension may change scoping rules as the assignment expression's target becomes local to the comprehension, potentially leaking or hiding variables differently.
  • If the generator expression is inside a lambda or nested function and uses nonlocal or global variables, moving the logic into a list comprehension could alter closure capture timing or semantics due to differences in how comprehensions vs generator expressions handle name resolution.
  • If the iterable side-effectfully mutates during iteration (e.g., list(x for x in foo where foo.remove(x))), the rewrite to list(foo) or a list comprehension may iterate over a different sequence because the original generator evaluated foo at start but the fix might re-evaluate or assume stability.
  • If the generator expression contains a 'break' or 'continue' statement (though invalid in generator expressions, consider via trickery), the fix would be invalid as comprehensions don't support those statements, but the rule shouldn't fire on invalid syntax anyway—still, if somehow present, the fix would break.
  • If the generator expression is used in a context where its lazy evaluation is relied upon for early termination via an enclosing construct (e.g., any(list(f(x) for x in foo))), replacing it with a list comprehension forces full evaluation upfront, changing performance and possibly causing errors if evaluation has side effects that should be avoided after a truthy result.
  • If the generator expression includes a comment inside the parentheses (e.g., list(x # comment for x in foo)), the autofix may drop the comment during rewrite because it only preserves tokens deemed essential, leading to loss of inline documentation.
  • If the generator expression is part of a larger expression where parentheses are significant for grouping (e.g., list((x for x in foo)) + 1), the fix might incorrectly simplify to list(foo) + 1, but if the inner parentheses were actually wrapping a tuple generator vs being redundant, the semantics of nesting could matter—though in practice the inner parens here are just around the generator, still, over-aggressive paren removal could theoretically affect parsing in edge cases involving complex expressions.
  • If the iterable is not actually a variable but a function call with side effects (e.g., list(x for x in get_foo())), rewriting to list(get_foo()) is safe, but if the fix mistakenly caches or reorders evaluation (which it shouldn't), it could call get_foo() multiple times vs once; however, the fix should preserve the single call, so this is more speculative—still, any refactoring tool must prove it doesn't introduce extra evaluations.
  • If the generator expression appears in a default argument or class body and relies on late binding, the fix timing of when the iterable is evaluated might shift relative to other definitions, though both generator expressions and comprehensions evaluate the iterable at the point of construction, so this is unlikely—but if the fix changed nesting depth, it could matter.
  • If the code contains a stray backslash for line continuation inside the generator expression (e.g., list(x\ for x in foo)), the autofix might mishandle the line joining during rewrite, breaking syntax or altering meaning.
  • If the generator expression is written with a trailing comma making it a tuple (e.g., list((x for x in foo),)), the rule likely wouldn't fire because it's not a plain generator call, but if misapplied, the fix would break structure.

Programs

idresulttitlehazard
r1-p001silent prediction offdict ordering, duplicate keys, __hash__/__eq__ overridesRewriting `list(...)` as `[...]` does not affect dictionary construction from key-value pairs generated lazily versus eagerly, which can alter duplicate key resolution timing.
r1-p002fired, no fixIterable side-effectful mutation during iterationWhen the source iterable mutates during iteration (e.g., list removes items), converting `list(x for x in foo where condition)` to `list(foo)` changes which elements are seen due to shifted indices or
r1-p003fired, no fixLazy evaluation for early termination via enclosing constructReplacing `list(f(x) for x in foo)` with `[f(x) for x in foo]` inside `any()` forces full evaluation even after a truthy value is found, changing side effect execution and potentially raising avoidabl
r1-p004silentasync def/await ordering, async generatorsThe rule does not apply to async generators, but if mistakenly used, rewriting would break await syntax; however we test a sync case near async code to show isolation.
r1-p005fired, no fixcomments and docstrings attached to the removed or moved codeWhen the rule removes the generator expression and rewraps the iterable, inline comments on the generator are lost, potentially removing documentation or TODO markers.
r1-p006fired, no fixevaluation order and side effects when expressions are merged, duplicated or deletedIf the generator expression contains a function call with side effects in its iterable part, moving it inside a list comprehension may change evaluation order relative to other expressions.
r1-p007silent prediction offexceptions raised inside the rewritten code, finally/else clauses, exception chainingIf the generator expression raises an exception during iteration, the timing of when it is caught (e.g., in a surrounding try) may differ between lazy and eager evaluation, affecting finally block exe
r1-p008fired, no fixbytes vs str, encoding and newline handling, text vs binary modesWhen the generator expression produces bytes or strings involved in encoding/decoding, eager vs lazy evaluation may change intermediate states when I/O operations are interleaved.
r2-p001fired, no fixGenerator consumption order mattersgenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p002fired, no fixComment lost during rewritecomments and docstrings attached to the removed or moved code
r2-p003fired, no fixSide effect in iterable function callIf the iterable is not actually a variable but a function call with side effects
r2-p004fired, no fixInline comment stripped in comprehensionIf the generator expression includes a comment inside the parentheses
r2-p005fired, no fixMutable default argument capturemutable default arguments and aliasing after a rewrite
r2-p006fired, no fixFile write truncation behaviorfile modes and truncation, partial writes, non-str data passed to write
r2-p007invalidBreak-like trick invalidates comprehensionIf the generator expression contains a 'break' or 'continue' statement
r2-p008fired, no fixType annotation evaluated at runtimetype annotations evaluated at runtime, typing-only imports

Sources

r1-p001 · dict ordering, duplicate keys, __hash__/__eq__ overrides
1class Key:
2 def __init__(self, v):
3 self.v = v
4 def __hash__(self):
5 return hash(self.v)
6 def __eq__(self, other):
7 print('eq', self.v)
8 return isinstance(other, Key) and self.v == other.v
9items = [Key(1), Key(1)]
10gen = ((k, k.v) for k in items)
11d = dict(list(gen))
12print(len(d))
eq 1
1
r1-p002 · Iterable side-effectful mutation during iteration
1foo = [1, 2, 3, 4]
2result = []
3def gen():
4 for x in foo[:]:
5 if x == 2:
6 foo.remove(3)
7 yield x
8for x in list(x for x in gen()):
9 result.append(x)
10print(result)
[1, 2, 3, 4]
r1-p003 · Lazy evaluation for early termination via enclosing construct
1def f(x):
2 print('f', x)
3 if x == 2:
4 raise ValueError('bad')
5 return x > 0
6try:
7 any(list(f(x) for x in [0, 1, 2, 3]))
8except ValueError as e:
9 print('ValueError:', e)
f 0
f 1
f 2
ValueError: bad
r1-p004 · async def/await ordering, async generators
1async def agen():
2 for i in range(3):
3 yield i
4async def main():
5 result = [i async for i in agen()]
6 print(result)
7import asyncio
8asyncio.run(main())
[0, 1, 2]
r1-p005 · comments and docstrings attached to the removed or moved code
1data = [1, 2, 3]
2# Process each item: transform and filter
3processed = list(x * 2 for x in data # <-- important: double values
4 if x % 2 == 0)
5print(processed)
[4]
r1-p006 · evaluation order and side effects when expressions are merged, duplicated or deleted
1side = []
2def get_data():
3 side.append('get_data')
4 return [1, 2]
5result = list(x + 1 for x in get_data())
6print('side:', side)
7print('result:', result)
side: ['get_data']
result: [2, 3]
r1-p007 · exceptions raised inside the rewritten code, finally/else clauses, exception chaining
1try:
2 gen = (1 / x for x in [0, 1, 2])
3 result = list(gen)
4except ZeroDivisionError as e:
5 print('Caught:', type(e).__name__, e)
6finally:
7 print('finally')
Caught: ZeroDivisionError division by zero
finally
r1-p008 · bytes vs str, encoding and newline handling, text vs binary modes
1lines = [b'hello\n', b'world\n']
2try:
3 decoded = list(line.decode('utf-8').strip() for line in lines)
4except UnicodeDecodeError as e:
5 print('UnicodeDecodeError:', e)
6else:
7 print('decoded:', decoded)
decoded: ['hello', 'world']
r2-p001 · Generator consumption order matters
1def gen():
2 yield 1
3 yield 2
4
5g = gen()
6a = list(x for x in g)
7b = list(x for x in g)
8print('a:', a, 'b:', b)
a: [1, 2] b: []
r2-p002 · Comment lost during rewrite
1# keep
2lst = list(
3 x # important note
4 for x in [1, 2]
5)
6print(lst)
[1, 2]
r2-p003 · Side effect in iterable function call
1counter = 0
2
3def get_foo():
4 global counter
5 counter += 1
6 return [10, 20]
7
8lst = list(x for x in get_foo())
9print('counter:', counter, 'lst:', lst)
counter: 1 lst: [10, 20]
r2-p004 · Inline comment stripped in comprehension
1data = [1, 2, 3]
2res = list(x # keep this
3 for x in data if x > 1)
4print(res)
[2, 3]
r2-p005 · Mutable default argument capture
1def f(lst=None):
2 if lst is None:
3 lst = []
4 lst.extend(list(x for x in [1, 2]))
5 return lst
6
7print(f())
8print(f())
[1, 2]
[1, 2]
r2-p006 · File write truncation behavior
1with open('test.txt', 'w') as f:
2 for item in list(str(x) for x in [1, 2, 3]):
3 f.write(item)
4with open('test.txt', 'r') as f:
5 print(f.read())
123
r2-p007 · Break-like trick invalidates comprehension (generated program does not parse: Traceback (most recent call last):)
1try:
2 # This syntax error is intentional for the hazard
3 list(x for x in [1, 2] if (break))
4except SyntaxError as e:
5 print('SyntaxError:', e)
r2-p008 · Type annotation evaluated at runtime
1T = int
2ansi = list(x for x in [1, 2, 3] if isinstance(x, T))
3print(ansi)
[1, 2, 3]