fixhunt

ruff RUF017

labelled unknown
Runs / 2026-10-17T21-38-53-735Z-6uly-ruff-RUF017ruff 0.17.12026-10-17 21:38 UTC8 programs × 2 round(s)16 programs, 11 fired, 0 fixed, 0 findings, 1 rejectedmodel cost $0.0062

Analysis Nemotron 3 Super

Flags calls to sum() used to flatten lists of lists by providing [] as the start argument.

Fix: Rewrites sum(lists, []) as [*sublist for sublist in lists] on Python 3.15+, otherwise as functools.reduce(operator.iadd, lists, []) after importing functools and operator if needed.

10 hazards
  • The rewrite accepts any iterable of iterables, whereas sum required actual lists, potentially changing behavior if non-list iterables are passed.
  • If the original sum call appeared in a context where its TypeError on non-list inputs was being caught and handled, the rewrite may silently succeed and alter control flow.
  • The fix may reorder evaluation: sum evaluates left-to-right with immediate accumulation, while the reduce form also evaluates left-to-right but via operator.iadd calls, which could differ if list concatenation had side effects (though unlikely for built-in lists).
  • On Python 3.15+, the starred form [*sublist for sublist in lists] creates the list via a comprehension, which may have different performance characteristics but identical results for list inputs.
  • If the variable name 'lists' is reused or shadowed in the surrounding scope, the fix’s comprehension or reduce expression could capture a different binding than the original sum call intended.
  • The fix may drop inline or trailing comments attached to the sum call line, as the rewrite replaces the entire expression and may not preserve comment tokens.
  • If the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger side effects at import time.
  • Using functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior, the result might diverge from sum’s repeated __add__ usage.
  • In rare cases where the start value [] was not a literal but a variable, the fix incorrectly hardcodes [], altering behavior if that variable was mutated elsewhere or intended to be dynamic.
  • If the code relied on the quadratic behavior of sum for timing or testing purposes, the rewrite changes performance characteristics, potentially breaking time-sensitive logic.

Programs

idresulttitlehazard
r1-p001fired, no fixEvaluation Order Side EffectThe fix may reorder evaluation: sum evaluates left-to-right with immediate accumulation, while the reduce form also evaluates left-to-right but via operator.iadd calls, which could differ if list conc
r1-p002fired, no fixStar Imports and Module Statestar-imports, module-level side effects, __all__
r1-p003fired, no fixNon-List IterablesThe rewrite accepts any iterable of iterables, whereas sum required actual lists, potentially changing behavior if non-list iterables are passed.
r1-p004silent prediction offCustom __iadd__ vs __add__Using functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior,
r1-p005fired, no fixImport-Time Side Effects in ExpressionIf the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger
r1-p006fired, no fixString Formatting Edge Casesstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r1-p007invalidKeyword-Only and Args Forwardingkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r1-p008fired, no fixGenerator Consumptiongenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p001fired, no fixType annotation evaluation with forward referencetype annotations evaluated at runtime, typing-only imports
r2-p002fired, no fixF-string with escaped braces and quote nestingstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r2-p003silentBinary file write with partial truncationfile modes and truncation, partial writes, non-str data passed to write
r2-p004fired, no fixVariable shadowing of 'lists' in nested scopeIf the variable name 'lists' is reused or shadowed in the surrounding scope, the fix’s comprehension or reduce expression could capture a different binding than the original sum call intended.
r2-p005fired, no fixTrailing comment on sum call line lost in fixThe fix may drop inline or trailing comments attached to the sum call line, as the rewrite replaces the entire expression and may not preserve comment tokens.
r2-p006silent prediction offCustom sequence with __iadd__ vs __add__ divergenceUsing functools.reduce(operator.iadd, ...) assumes the elements support in-place addition via __iadd__; for lists this works, but if the iterables were custom sequences with unusual __iadd__ behavior,
r2-p007fired, no fixSum call inside function argument triggering module-level importIf the sum call was part of a larger expression (e.g., inside a function argument or conditional), the fix’s insertion of import statements (for reduce form) could affect module-level state or trigger
r2-p008silent prediction offBytes vs str in sum with binary mode interactionbytes vs str, encoding and newline handling, text vs binary modes

Sources

r1-p001 · Evaluation Order Side Effect
1class LogList(list):
2 def __init__(self, data, name):
3 super().__init__(data)
4 self.name = name
5 def __add__(self, other):
6 print(f'add {self.name} + {getattr(other, "name", "?")}')
7 return super().__add__(other)
8 def __iadd__(self, other):
9 print(f'iadd {self.name} += {getattr(other, "name", "?")}')
10 return super().__iadd__(other)
11
12lst1 = LogList([1], 'A')
13lst2 = LogList([2], 'B')
14lst3 = LogList([3], 'C')
15print('sum:')
16result = sum([lst1, lst2, lst3], [])
17print('reduce-like:')
18# Manual reduce for comparison
19from functools import reduce
20import operator
21result2 = reduce(operator.iadd, [lst1, lst2, lst3], [])
22print('Results equal:', result == result2)
sum:
reduce-like:
Results equal: True
r1-p002 · Star Imports and Module State
1# Simulate a module with side effects on import
2import sys
3if 'hazard_mod' not in sys.modules:
4 exec("""
5# hazard_mod.py
6_side_effect = []
7def register(x):
8 _side_effect.append(x)
9 return x
10__all__ = ['register']
11""", {'__name__': 'hazard_mod'})
12 sys.modules['hazard_mod'] = sys.modules.get('hazard_mod', type('hazard_mod', (), {}))
13 hazard_mod = sys.modules['hazard_mod']
14 hazard_mod._side_effect = []
15 hazard_mod.register = lambda x: (hazard_mod._side_effect.append(x), x)[1]
16 hazard_mod.__all__ = ['register']
17
18from hazard_mod import * # star-import
19
20lists = [[1], [2], [3]]
21print('Before sum:', hazard_mod._side_effect)
22result = sum(lists, [])
23print('After sum:', hazard_mod._side_effect)
24print('Result:', result)
Before sum: []
After sum: []
Result: [1, 2, 3]
r1-p003 · Non-List Iterables
1def gen_lists():
2 yield [1, 2]
3 yield (3, 4) # tuple, not list
4 yield {5, 6} # set, not list
5
6print('Input:')
7for item in gen_lists():
8 print(f' {item} ({type(item).__name__})')
9
10try:
11 result = sum(gen_lists(), [])
12 print('sum result:', result, type(result))
13except Exception as e:
14 print('sum error:', type(e).__name__, str(e))
15
16# Reset generator
17print('\nReduce form:')
18from functools import reduce
19import operator
20result2 = reduce(operator.iadd, gen_lists(), [])
21print('reduce result:', result2, type(result2))
Input:
  [1, 2] (list)
  (3, 4) (tuple)
  {5, 6} (set)
sum error: TypeError can only concatenate list (not "tuple") to list

Reduce form:
reduce result: [1, 2, 3, 4, 5, 6] <class 'list'>
r1-p004 · Custom __iadd__ vs __add__
1class WeirdSeq:
2 def __init__(self, data, label):
3 self.data = list(data)
4 self.label = label
5 def __add__(self, other):
6 # Always returns a fresh list, ignores self
7 return list(getattr(other, 'data', [])) if hasattr(other, 'data') else list(other)
8 def __iadd__(self, other):
9 # Mutates self to be a copy of other
10 self.data.clear()
11 self.data.extend(getattr(other, 'data', []) if hasattr(other, 'data') else other)
12 return self
13 def __iter__(self):
14 return iter(self.data)
15 def __repr__(self):
16 return f'WeirdSeq({self.data}, {self.label})'
17
18a = WeirdSeq([1, 2], 'a')
19b = WeirdSeq([3, 4], 'b')
20c = WeirdSeq([5, 6], 'c')
21
22print('Initial:', a, b, c)
23print('\nUsing sum:')
24try:
25 s = sum([a, b, c], WeirdSeq([], 'start'))
26 print('Result:', s)
27 print('a,b,c after:', a, b, c)
28except Exception as e:
29 print('Error:', type(e).__name__, e)
30
31# Reset
32a = WeirdSeq([1, 2], 'a')
33b = WeirdSeq([3, 4], 'b')
34c = WeirdSeq([5, 6], 'c')
35print('\nUsing reduce:')
36from functools import reduce
37import operator
38try:
39 r = reduce(operator.iadd, [a, b, c], WeirdSeq([], 'start'))
40 print('Result:', r)
41 print('a,b,c after:', a, b, c)
42except Exception as e:
43 print('Error:', type(e).__name__, e)
Initial: WeirdSeq([1, 2], a) WeirdSeq([3, 4], b) WeirdSeq([5, 6], c)

Using sum:
Error: TypeError can only concatenate list (not "WeirdSeq") to list

Using reduce:
Result: WeirdSeq([5, 6], start)
a,b,c after: WeirdSeq([1, 2], a) WeirdSeq([3, 4], b) WeirdSeq([5, 6], c)
r1-p005 · Import-Time Side Effects in Expression
1import sys
2
3# Track if hazardous module is imported
4import_side_effects = []
5
6# Simulate importing a module with side effects
7if 'effect_mod' not in sys.modules:
8 mod = type('effect_mod', (), {})
9 mod._import_log = []
10 def init_side():
11 mod._import_log.append('initialized')
12 return 42
13 mod.CONSTANT = init_side()
14 sys.modules['effect_mod'] = mod
15
16effect_mod = sys.modules['effect_mod']
17
18def get_lists():
19 # Trigger import of effect_mod when called
20 _ = effect_mod.CONSTANT # access to trigger if not already
21 return [[1], [2]]
22
23print('Before call:', effect_mod._import_log)
24try:
25 # This sum call will trigger the fix -> add imports -> but we simulate
26 # that the fix would insert imports HERE, which could cause side effects
27 result = sum(get_lists(), [])
28 print('After sum:', effect_mod._import_log)
29 print('Result:', result)
30except Exception as e:
31 print('Error:', type(e).__name__, e)
Before call: ['initialized']
After sum: ['initialized']
Result: [1, 2]
r1-p006 · String Formatting Edge Cases
1lists = [['a', 'b'], ['c', 'd']]
2flattened = sum(lists, [])
3print('Flattened:', flattened)
4
5# Test various formatting
6s = ''.join(flattened)
7print('%% formatting:', '%s' % s)
8print('.format():', '{}'.format(s))
9print('f-string:', f'{s}')
10print('repr:', repr(s))
11
12# Nested quotes in f-string
13quote_test = f'He said "{''.join(flattened)}"'
14print('Nested quote f-string:', quote_test)
15
16# Format with special chars
17try:
18 formatted = '{!r:{}}'.format(s, 10)
19 print('Format spec:', formatted)
20except Exception as e:
21 print('Format error:', type(e).__name__, e)
Flattened: ['a', 'b', 'c', 'd']
%% formatting: abcd
.format(): abcd
f-string: abcd
repr: 'abcd'
Nested quote f-string: He said "abcd"
Format spec: 'abcd'
r1-p007 · Keyword-Only and Args Forwarding (generated program does not parse: Traceback (most recent call last):)
1default_val = []
2def append_to_lists(*args, source=default_val, , **kwargs):
3 # Note: invalid syntax - fix below
4 pass
5
6# Correct version:
7def process_data(*arrays, source=None, , **opts):
8 if source is None:
9 source = []
10 flattened = sum(arrays, source)
11 opts.setdefault('processed', True)
12 return flattened, opts
13
14# Hazard: default_val mutable
15print('Initial default_val:', id(default_val))
16result1, opts1 = process_data([1, 2], [3, 4])
17print('After first call:', id(default_val), default_val)
18result2, opts2 = process_data([5, 6])
19print('After second call:', id(default_val), default_val)
20print('Results:', result1, result2)
21print('Opts:', opts1, opts2)
r1-p008 · Generator Consumption
1def make_gen():
2 print(' Generator started')
3 for i in range(3):
4 print(f' Yielding [{i}, {i+1}]')
5 yield [i, i+1]
6 print(' Generator ended')
7
8print('First sum:')
9gen = make_gen()
10result1 = sum(gen, [])
11print('Result:', result1)
12
13print('\nSecond sum (same generator):')
14gen2 = make_gen() # new generator
15result2 = sum(gen2, [])
16print('Result:', result2)
17
18print('\nManual reduce for comparison:')
19from functools import reduce
20import operator
21gen3 = make_gen()
22result3 = reduce(operator.iadd, gen3, [])
23print('Reduce result:', result3)
First sum:
  Generator started
  Yielding [0, 1]
  Yielding [1, 2]
  Yielding [2, 3]
  Generator ended
Result: [0, 1, 1, 2, 2, 3]

Second sum (same generator):
  Generator started
  Yielding [0, 1]
  Yielding [1, 2]
  Yielding [2, 3]
  Generator ended
Result: [0, 1, 1, 2, 2, 3]

Manual reduce for comparison:
  Generator started
  Yielding [0, 1]
  Yielding [1, 2]
  Yielding [2, 3]
  Generator ended
Reduce result: [0, 1, 1, 2, 2, 3]
r2-p001 · Type annotation evaluation with forward reference
1from __future__ import annotations
2
3def process_lists(lists):
4 return sum(lists, []) # flatten list of int pairs
5
6pairs: list[tuple[int, int]] = [(1, 2), (3, 4)]
7result = process_lists(pairs)
8print(result)
r2-p002 · F-string with escaped braces and quote nesting
1data = [[1], [2, 3]]
2flattened = sum(data, []) # {{ flattened }}
3print(f"Result: {flattened}")
Result: [1, 2, 3]
r2-p003 · Binary file write with partial truncation
1import os
2if os.path.exists('test.bin'):
3 os.remove('test.bin')
4with open('test.bin', 'wb') as f:
5 chunks = [b'hello', b' ', b'world']
6 written = sum(chunks, b'') # flatten byte chunks
7 f.write(written)
8with open('test.bin', 'rb') as f:
9 print(f.read())
10if os.path.exists('test.bin'):
11 os.remove('test.bin')
r2-p004 · Variable shadowing of 'lists' in nested scope
1lists = [[1, 2], [3, 4]]
2result1 = sum(lists, []) # outer lists
3for lists in [[5, 6]]: # shadows outer lists
4 result2 = sum(lists, []) # inner lists
5print(result1, result2)
r2-p005 · Trailing comment on sum call line lost in fix
1data = [[1, 2], [3]]
2flat = sum(data, []) # flatten for printing
3print(flat) # should be [1,2,3]
[1, 2, 3]
r2-p006 · Custom sequence with __iadd__ vs __add__ divergence
1class Vec:
2 def __init__(self, x):
3 self.x = x
4 def __add__(self, other):
5 return Vec(self.x + other.x)
6 def __iadd__(self, other):
7 self.x += other.x * 2 # different behavior!
8 return self
9 def __repr__(self):
10 return f'Vec({self.x})'
11
12items = [Vec(1), Vec(2)]
13start = Vec(0)
14result_sum = sum(items, start) # uses __add__
15# After fix: reduce(iadd, items, start) uses __iadd__
16print('sum result:', result_sum)
17print('start after sum:', start) # start mutated if iadd used
sum result: Vec(3)
start after sum: Vec(0)
r2-p007 · Sum call inside function argument triggering module-level import
1def consume(x):
2 print('consumed:', x)
3
4# Trigger functools.reduce import via sum fix
5consume(sum([[1], [2]], [])) # should print [1, 2]
6print('done')
consumed: [1, 2]
done
r2-p008 · Bytes vs str in sum with binary mode interaction
1chunks = [b'ab', b'cd']
2result = sum(chunks, b'') # bytes concatenation
3print('bytes result:', result)
4try:
5 sum([[1, 2]], '') # mixing list and str start - should fail
6except TypeError as e:
7 print('error:', type(e).__name__, str(e))