fixhunt

ruff FURB145

labelled unknown
Runs / 2026-10-17T21-32-36-205Z-tk0u-ruff-FURB145ruff 0.17.12026-10-17 21:32 UTC8 programs × 2 round(s)10 programs, 2 fired, 2 fixed, 0 findings, 1 rejectedmodel cost $0.0056

Analysis Nemotron 3 Super

Flags unbounded slice expressions (`seq[:]`) used to copy a sequence when the target is known to be a list.

Fix: Replaces the unbounded slice expression `seq[:]` with the method call `seq.copy()` while preserving the surrounding assignment or expression context.

11 hazards
  • Rewriting `seq[:]` where `seq` is not a list (e.g., a string or tuple) changes the return type from the original sequence type to a list, breaking type expectations.
  • If `seq` is a subclass of list that overrides `__getitem__` but not `copy`, the slice may return a different type than `copy()` produces.
  • When the slice appears in a context relying on the exact object identity of a new list (e.g., `is` checks), `copy()` may produce a different object than slicing in some implementations (though CPython guarantees a new object, this is not language-spec guaranteed).
  • If `seq` is a variable that is deleted or reassigned between the evaluation of the slice and its use in the original code (impossible in a simple expression, but relevant in larger refactorings), the fix does not capture this timing—however, since the fix is applied locally to an expression, this hazard is mitigated; still noted for completeness in complex macro-like scenarios.
  • If the slice expression contains an inline comment (e.g., `a[: # copy]`), the fix may drop or misplace the comment during rewrite, altering source code readability.
  • In code where `seq` is a property with a side-effecting `__getitem__` (highly unconventional but possible), accessing `seq[:]` triggers `__getitem__` once with a slice object, whereas `seq.copy()` may not invoke `__getitem__` at all, changing side-effect timing.
  • If `seq` is a mutable object and the slice is used in a conditional expression where the object might be mutated between evaluation points (not applicable here as the expression is atomic), but note that the fix does not alter evaluation order—it replaces the expression in place.
  • When the slice is part of a larger subscript (e.g., `a[:][0]`), the fix only applies to the unbounded slice if isolated; however, the rule does not fire on bounded or chained slices, so this shape is silent—no hazard from misapplication.
  • If `seq` is annotated as a list but inferred as a subtype (e.g., `UserList`), and `copy()` is not overridden, the returned object may lack expected subclass behavior, though `list.copy()` on a subclass returns a plain list, whereas slicing may preserve subclass type—this is a behavioral change.
  • In code that depends on the slice returning exactly the same type as the input (e.g., for duck typing with custom sequences), replacing with `copy()` narrows the type to `list`, breaking polymorphism.
  • If the slice appears in a lambda or comprehension where the variable is rebound later (e.g., `[f() for x in data][:]` vs `[f() for x in data.copy()]`), the fix does not change behavior because the slice is evaluated immediately—but note: the rule does not fire on `data[:]` inside a comprehension index; it fires on standalone unbounded slices.

Programs

idresulttitlehazard
r1-p001silent prediction offSubclass override mismatchIf seq is a subclass of list that overrides __getitem__ but not copy, the slice may return a different type than copy() produces.
r1-p002silent prediction offPolymorphism narrowingIn code that depends on the slice returning exactly the same type as the input (e.g., for duck typing with custom sequences), replacing with copy() narrows the type to list, breaking polymorphism.
r1-p003silent prediction offMutable default and aliasingmutable default arguments and aliasing after a rewrite
r1-p004silent prediction offParameter kinds and defaultskeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r1-p005silent prediction offDict ordering and overridesdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r1-p006fix okAsync interactionasync def/await ordering, async generators
r1-p007silentFile write truncationfile modes and truncation, partial writes, non-str data passed to write
r1-p008silent prediction offExceptions and finallyexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r2-p001invalidConditional expression mutation timingConditional expression mutation timing - fix doesn't alter eval order but we verify sequencing by interleaving state reads/writes around the ternary op.
r2-p002fix ok prediction offIdentity check relianceObject identity reliance: user expects distinct objects via [:], .copy() also gives distinct but future impl could differ; we detect identity via 'is'.

Sources

r1-p001 · Subclass override mismatch
1class MyList(list):
2 def __getitem__(self, key):
3 return super().__getitem__(key) * 2
4lst = MyList([1, 2, 3])
5slice_copy = lst[:]
6method_copy = lst.copy()
7print('slice type:', type(slice_copy).__name__, 'values:', slice_copy)
8print('method type:', type(method_copy).__name__, 'values:', method_copy)
slice type: list values: [1, 2, 3, 1, 2, 3]
method type: list values: [1, 2, 3]
r1-p002 · Polymorphism narrowing
1class Seq:
2 def __init__(self, data):
3 self.data = list(data)
4 def __getitem__(self, key):
5 return self.data[key]
6 def __len__(self):
7 return len(self.data)
8 def copy(self):
9 raise AttributeError('no copy')
10s = Seq([1, 2, 3])
11try:
12 sliced = s[:]
13except TypeError as e:
14 sliced = None
15 print('slice error:', type(e).__name__, str(e))
16try:
17 copied = s.copy()
18except AttributeError as e:
19 copied = None
20 print('copy error:', type(e).__name__, str(e))
21print('slice result:', type(sliced).__name__ if sliced else 'None')
22print('copy result:', type(copied).__name__ if copied else 'None')
copy error: AttributeError no copy
slice result: list
copy result: None
r1-p003 · Mutable default and aliasing
1def f(x=None):
2 if x is None:
3 x = []
4 y = x[:]
5 y.append(99)
6 return x, y
7a, b = f()
8print('first call:', a, b)
9c, d = f()
10print('second call:', c, d)
first call: [] [99]
second call: [] [99]
r1-p004 · Parameter kinds and defaults
1def process(a, /, b, *, c=None):
2 if c is None:
3 c = b[:]
4 c.append(len(b))
5 return a, b, c
6out = process(1, [2, 3], c=[4])
7print('out:', out)
out: (1, [2, 3], [4, 2])
r1-p005 · Dict ordering and overrides
1class Key:
2 def __init__(self, val):
3 self.val = val
4 def __hash__(self):
5 return hash(self.val)
6 def __eq__(self, other):
7 return self.val == other.val
8k1 = Key(1)
9k2 = Key(1)
10d = {k1: 'first', k2: 'second'}
11items = list(d.items())[:]
12print('dict len:', len(d), 'slice len:', len(items))
13for k, v in items:
14 print(k.val, v)
dict len: 1 slice len: 1
1 second
r1-p006 · Async interaction
1async def gen():
2 for i in [1, 2, 3]:
3 yield i
4async def main():
5 g = [x async for x in gen()] # materialize async gen
6 slice_part = g[:]
7 method_part = g.copy()
8 print('slice:', slice_part, 'type:', type(slice_part).__name__)
9 print('copy:', method_part, 'type:', type(method_part).__name__)
10import asyncio
11asyncio.run(main())
--- orig.py	2026-10-17 17:32:47.514914824 -0400
+++ fixed.py	2026-10-17 17:32:47.811910782 -0400
@@ -3,7 +3,7 @@
         yield i
 async def main():
     g = [x async for x in gen()]  # materialize async gen
-    slice_part = g[:]
+    slice_part = g.copy()
     method_part = g.copy()
     print('slice:', slice_part, 'type:', type(slice_part).__name__)
     print('copy:', method_part, 'type:', type(method_part).__name__)
slice: [1, 2, 3] type: list
copy: [1, 2, 3] type: list
r1-p007 · File write truncation
1data = b'hello'
2with open('test.bin', 'wb') as f:
3 f.write(data[:]) # rule does not trigger on bytes
4with open('test.bin', 'rb') as f:
5 print(f.read())
6import os
7os.remove('test.bin')
b'hello'
r1-p008 · Exceptions and finally
1class BadList(list):
2 def __getitem__(self, key):
3 if key == 0:
4 raise ValueError('bad')
5 return super().__getitem__(key)
6 def copy(self):
7 return super().copy()
8b = BadList([1, 2, 3])
9try:
10 x = b[:] # triggers on first element access during slice
11 print('slice succeeded:', x)
12except ValueError as e:
13 print('slice error:', type(e).__name__, str(e))
14try:
15 y = b.copy()
16 print('copy succeeded:', y)
17except ValueError as e:
18 print('copy error:', type(e).__name__, str(e))
slice succeeded: [1, 2, 3]
copy succeeded: [1, 2, 3]
r2-p001 · Conditional expression mutation timing (generated program is not deterministic)
1state = []
2def get_target(): return state[:]
3state.append(1)
4a = [] if False else get_target()
5print('result id', id(a))
6print('source len', len(state))
r2-p002 · Identity check reliance
1src = [1, 2]
2candidate = src[:]
3is_same_obj = candidate is src
4print('distinct?', not is_same_obj)
5# rule triggers on candidate = src[:]
6if not is_same_obj:
7 print('ok')
8else:
9 print('unexpected alias')
--- orig.py	2026-10-17 17:33:01.807722136 -0400
+++ fixed.py	2026-10-17 17:33:01.970719959 -0400
@@ -1,5 +1,5 @@
 src = [1, 2]
-candidate = src[:]
+candidate = src.copy()
 is_same_obj = candidate is src
 print('distinct?', not is_same_obj)
 # rule triggers on candidate = src[:]
distinct? True
ok