fixhunt

ruff B010

labelled unknown
Runs / 2026-10-17T20-11-38-784Z-baf8-ruff-B010ruff 0.17.12026-10-17 20:11 UTC8 programs × 2 round(s)16 programs, 14 fired, 13 fixed, 0 findings, 0 rejectedmodel cost $0.0061

Analysis Nemotron 3 Super

Flags setattr() calls where the attribute name argument is a literal string.

Fix: Rewrites setattr(obj, "attr", value) to obj.attr = value, preserving the target object, attribute name literal, and value expression exactly.

11 hazards
  • Rewriting setattr(obj, "ſ", 1) to obj.ſ = 1 changes behavior because Python normalizes identifiers in attribute access via NFKC, turning "ſ" into "s".
  • If the setattr call contains inline comments (e.g., setattr(x, "a", 1 # comment)), the autofix may drop them since the rewritten assignment cannot preserve arbitrary comment placement.
  • When the target object is a complex expression like setattr(f()[0], "a", 1), rewriting to f()[0].a = 1 may change evaluation order or side effects if f() has mutable state.
  • If the attribute name literal is not a valid Python identifier (e.g., setattr(obj, "123", 1)), the rewrite obj.123 = 1 is a syntax error.
  • Rewriting setattr(obj, "__builtins__", value) to obj.__builtins__ = value may violate module-level immutability expectations or trigger unintended name mangling in classes.
  • In code relying on dynamic attribute names via setattr, replacing with direct assignment may break frameworks that intercept setattr via __setattr__ but not direct attribute assignment.
  • If the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder expressions unsafely in broader contexts.
  • When used in nested scopes like setattr(lambda: obj, "a", 1), the rewrite (lambda: obj).a = 1 may fail if the lambda’s return value is not assignable due to expression context restrictions.
  • Rewriting setattr(klass, "ClsAttr", 42) to klass.ClsAttr = 42 inside a function may create an attribute on the class object at runtime where the original intended a local or closure variable due to mis-scoping.
  • If the target object is a property returning a non-setattr-compatible object (e.g., a read-only proxy), the assignment may succeed where setattr would have raised an AttributeError due to __setattr__ overrides.
  • In multi-target assignments like setattr(a, "x", setattr(b, "y", 1)), the autofix may produce invalid syntax or altered semantics due to broken expression chaining.

Programs

idresulttitlehazard
r1-p001fix okException handling after rewriteExceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p002fix okEvaluation order with side-effecting targetWhen the target object is a complex expression like setattr(f()[0], "a", 1), rewriting to f()[0].a = 1 may change evaluation order or side effects if f() has mutable state.
r1-p003fix okMis-scoped attribute creation on classRewriting setattr(klass, "ClsAttr", 42) to klass.ClsAttr = 42 inside a function may create an attribute on the class object at runtime where the original intended a local or closure variable due to mi
r1-p004fix okBuiltins override via attribute assignmentRewriting setattr(obj, "__builtins__", value) to obj.__builtins__ = value may violate module-level immutability expectations or trigger unintended name mangling in classes.
r1-p005fix okValue expression with side effectsIf the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder express
r1-p006silent prediction offChained setattr in assignmentIn multi-target assignments like setattr(a, "x", setattr(b, "y", 1)), the autofix may produce invalid syntax or altered semantics due to broken expression chaining.
r1-p007fix okFile write truncation and partial writesfile modes and truncation, partial writes, non-str data passed to write
r1-p008fix okTruthiness and equality distinctionstruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='
r2-p001fix okProperty returns read-only proxy blocking assignmentIf the target object is a property returning a non-setattr-compatible object (e.g., a read-only proxy), the assignment may succeed where setattr would have raised an AttributeError due to __setattr__
r2-p002fix okBinary mode file write with str vs bytes confusionbytes vs str, encoding and newline handling, text vs binary modes
r2-p003fix okText mode file write truncation and partial write with newline translationfile modes and truncation, partial writes, non-str data passed to write
r2-p004fix okFloat precision loss in assignment vs setattr contextint/float/bool coercion, Decimal, division and rounding
r2-p005fix okStar-import shadowing __all__ and triggering attribute write to modulestar-imports, module-level side effects, __all__
r2-p006fix okMutable default argument aliasing after rewritemutable default arguments and aliasing after a rewrite
r2-p007fired, no fixInline comment lost during autofix rewriteIf the setattr call contains inline comments (e.g., setattr(x, "a", 1 # comment)), the autofix may drop them since the rewritten assignment cannot preserve arbitrary comment placement.
r2-p008silent prediction offSide effect in value expression: print during setattr vs assignmentIf the value expression contains side effects (e.g., setattr(obj, "a", print("side") or 1)), moving it into an assignment does not change evaluation timing, but refactoring tools might reorder express

Sources

r1-p001 · Exception handling after rewrite
1def f():
2 try:
3 x = []
4 setattr(x, 'append', lambda v: (_ for _ in ()).throw(ValueError('bad')))
5 x.append(1)
6 except ValueError as e:
7 print('caught', e)
8 else:
9 print('no exc')
10 finally:
11 print('finally')
12f()
--- orig.py	2026-10-17 16:11:51.039178352 -0400
+++ fixed.py	2026-10-17 16:11:51.423168980 -0400
@@ -1,7 +1,7 @@
 def f():
     try:
         x = []
-        setattr(x, 'append', lambda v: (_ for _ in ()).throw(ValueError('bad')))
+        x.append = lambda v: (_ for _ in ()).throw(ValueError('bad'))
         x.append(1)
     except ValueError as e:
         print('caught', e)
finally
r1-p002 · Evaluation order with side-effecting target
1state = []
2def f():
3 state.append(len(state))
4 return [0, 0]
5setattr(f(), 'a', 99)
6print('state:', state)
--- orig.py	2026-10-17 16:11:51.623164099 -0400
+++ fixed.py	2026-10-17 16:11:52.026154265 -0400
@@ -2,5 +2,5 @@
 def f():
     state.append(len(state))
     return [0, 0]
-setattr(f(), 'a', 99)
+f().a = 99
 print('state:', state)
r1-p003 · Mis-scoped attribute creation on class
1def outer():
2 ClsAttr = 1
3 class C: pass
4 def inner():
5 setattr(C, 'ClsAttr', 42)
6 print('local ClsAttr:', ClsAttr)
7 print('C.ClsAttr:', C.ClsAttr)
8 inner()
9outer()
--- orig.py	2026-10-17 16:11:52.226149395 -0400
+++ fixed.py	2026-10-17 16:11:52.372145840 -0400
@@ -2,7 +2,7 @@
     ClsAttr = 1
     class C: pass
     def inner():
-        setattr(C, 'ClsAttr', 42)
+        C.ClsAttr = 42
         print('local ClsAttr:', ClsAttr)
         print('C.ClsAttr:', C.ClsAttr)
     inner()
local ClsAttr: 1
C.ClsAttr: 42
r1-p004 · Builtins override via attribute assignment
1class ModLike:
2 pass
3m = ModLike()
4setattr(m, '__builtins__', {'len': lambda x: 42})
5print('m.__builtins__["len"]([1,2,3]):', m.__builtins__['len']([1,2,3]))
--- orig.py	2026-10-17 16:11:52.452143892 -0400
+++ fixed.py	2026-10-17 16:11:52.612139996 -0400
@@ -1,5 +1,5 @@
 class ModLike:
     pass
 m = ModLike()
-setattr(m, '__builtins__', {'len': lambda x: 42})
+m.__builtins__ = {'len': lambda x: 42}
 print('m.__builtins__["len"]([1,2,3]):', m.__builtins__['len']([1,2,3]))
m.__builtins__["len"]([1,2,3]): 42
r1-p005 · Value expression with side effects
1log = []
2def side():
3 log.append('side')
4 return 1
5obj = type('O', (), {})()
6setattr(obj, 'a', side() or 0)
7print('log:', log)
8print('obj.a:', obj.a)
--- orig.py	2026-10-17 16:11:52.704137756 -0400
+++ fixed.py	2026-10-17 16:11:52.862133908 -0400
@@ -3,6 +3,6 @@
     log.append('side')
     return 1
 obj = type('O', (), {})()
-setattr(obj, 'a', side() or 0)
+obj.a = side() or 0
 print('log:', log)
 print('obj.a:', obj.a)
log: ['side']
obj.a: 1
r1-p006 · Chained setattr in assignment
1class A: pass
2class B: pass
3a = A()
4b = B()
5result = setattr(a, 'x', setattr(b, 'y', 1))
6print('a.x:', a.x)
7print('b.y:', b.y)
8print('result:', result)
a.x: None
b.y: 1
result: None
r1-p007 · File write truncation and partial writes
1with open('test.txt', 'w') as f:
2 setattr(f, 'write', lambda data: f.__write__(data.upper()))
3 f.write('hi')
4with open('test.txt') as f:
5 print(f.read())
6import os; os.remove('test.txt')
--- orig.py	2026-10-17 16:11:53.109127900 -0400
+++ fixed.py	2026-10-17 16:11:53.535117550 -0400
@@ -1,5 +1,5 @@
 with open('test.txt', 'w') as f:
-    setattr(f, 'write', lambda data: f.__write__(data.upper()))
+    f.write = lambda data: f.__write__(data.upper())
     f.write('hi')
 with open('test.txt') as f:
     print(f.read())
r1-p008 · Truthiness and equality distinctions
1class X:
2 def __bool__(self): return False
3 def __len__(self): return 1
4obj = type('O', (), {})()
5setattr(obj, 'flag', X())
6if obj.flag:
7 print('true')
8else:
9 print('false but len=', len(obj.flag))
10print('obj.flag is False:', obj.flag is False)
--- orig.py	2026-10-17 16:11:53.752112278 -0400
+++ fixed.py	2026-10-17 16:11:53.914108342 -0400
@@ -2,7 +2,7 @@
     def __bool__(self): return False
     def __len__(self): return 1
 obj = type('O', (), {})()
-setattr(obj, 'flag', X())
+obj.flag = X()
 if obj.flag:
     print('true')
 else:
false but len= 1
obj.flag is False: False
r2-p001 · Property returns read-only proxy blocking assignment
1class ReadOnlyProxy:
2 def __setattr__(self, name, value):
3 raise AttributeError('read-only')
4
5class Obj:
6 @property
7 def p(self):
8 return ReadOnlyProxy()
9
10obj = Obj()
11try:
12 setattr(obj.p, 'x', 1)
13except AttributeError as e:
14 print('setattr error:', type(e).__name__, ':', e)
15else:
16 print('setattr succeeded')
17
18try:
19 obj.p.x = 1 # rewritten by autofix
20except AttributeError as e:
21 print('assignment error:', type(e).__name__, ':', e)
22else:
23 print('assignment succeeded')
--- orig.py	2026-10-17 16:12:15.983585807 -0400
+++ fixed.py	2026-10-17 16:12:16.143582116 -0400
@@ -9,7 +9,7 @@
 
 obj = Obj()
 try:
-    setattr(obj.p, 'x', 1)
+    obj.p.x = 1
 except AttributeError as e:
     print('setattr error:', type(e).__name__, ':', e)
 else:
setattr error: AttributeError : read-only
assignment error: AttributeError : read-only
r2-p002 · Binary mode file write with str vs bytes confusion
1f = open('test.bin', 'wb')
2try:
3 setattr(f, 'mode', 'wb') # rule triggers
4 f.write(b'hello')
5 pos = f.tell()
6 print('position after write:', pos)
7except Exception as e:
8 print('error during setattr/write:', type(e).__name__, ':', e)
9f.close()
10
11f2 = open('test.bin', 'rb')
12print('file content:', f2.read())
13f2.close()
14import os
15os.remove('test.bin')
--- orig.py	2026-10-17 16:12:16.242579833 -0400
+++ fixed.py	2026-10-17 16:12:16.395576304 -0400
@@ -1,6 +1,6 @@
 f = open('test.bin', 'wb')
 try:
-    setattr(f, 'mode', 'wb')  # rule triggers
+    f.mode = 'wb'  # rule triggers
     f.write(b'hello')
     pos = f.tell()
     print('position after write:', pos)
error during setattr/write: AttributeError : attribute 'mode' of '_io.BufferedWriter' objects is not writable
file content: b''
r2-p003 · Text mode file write truncation and partial write with newline translation
1f = open('test.txt', 'w')
2try:
3 setattr(f, 'encoding', 'utf-8')
4except: pass
5f.write('line1\n')
6f.write('line2') # no newline yet
7pos = f.tell()
8f.close()
9print('position before close:', pos)
10
11f = open('test.txt', 'r')
12content = f.read()
13f.close()
14print('content repr:', repr(content))
15import os
16os.remove('test.txt')
--- orig.py	2026-10-17 16:12:16.490574114 -0400
+++ fixed.py	2026-10-17 16:12:16.647570493 -0400
@@ -1,6 +1,6 @@
 f = open('test.txt', 'w')
 try:
-    setattr(f, 'encoding', 'utf-8')
+    f.encoding = 'utf-8'
 except: pass
 f.write('line1\n')
 f.write('line2')  # no newline yet
position before close: 11
content repr: 'line1\nline2'
r2-p004 · Float precision loss in assignment vs setattr context
1val = 0.1 + 0.2
2setattr_obj = type('Obj', (), {})()
3setattr(setattr_obj, 'num', val)
4print('setattr num:', repr(setattr_obj.num))
5
6obj = type('Obj', (), {})()
7obj.num = val # rewritten by autofix
8print('assignment num:', repr(obj.num))
9
10# Show difference in formatting
11print('equal?', setattr_obj.num == obj.num)
12print('hex(setattr):', hex(float.fromhex(repr(setattr_obj.num).replace('0.', '0x')) if setattr_obj.num != 0 else '0x0'))
13print('hex(assign):', hex(float.fromhex(repr(obj.num).replace('0.', '0x')) if obj.num != 0 else '0x0'))
--- orig.py	2026-10-17 16:12:16.748568164 -0400
+++ fixed.py	2026-10-17 16:12:17.161558648 -0400
@@ -1,6 +1,6 @@
 val = 0.1 + 0.2
 setattr_obj = type('Obj', (), {})()
-setattr(setattr_obj, 'num', val)
+setattr_obj.num = val
 print('setattr num:', repr(setattr_obj.num))
 
 obj = type('Obj', (), {})()
setattr num: 0.30000000000000004
assignment num: 0.30000000000000004
equal? True
r2-p005 · Star-import shadowing __all__ and triggering attribute write to module
1# This file: mod.py
2__all__ = ['public']
3private = 42
4public = 88
5# End mod.py
6
7import os
8os.system('python3 -c "\n__all__ = [\"public\"]\nprivate = 42\npublic = 88\n" > mod.py')
9
10from mod import * # imports only public
11print('after star-import: public=', public, 'private exists?', 'private' in dir())
12
13# Now simulate: setattr(mod, '__all__', ['private']) triggers rule
14import mod
15setattr(mod, '__all__', ['private']) # rule fires
16print('after setattr __all__:', mod.__all__)
17
18# Now star-import again: should import private
19from mod import *
20print('after second star-import: public=', public, 'private=', private if 'private' in locals() else 'not imported')
21os.remove('mod.py')
--- orig.py	2026-10-17 16:12:17.385553494 -0400
+++ fixed.py	2026-10-17 16:12:18.063537898 -0400
@@ -12,7 +12,7 @@
 
 # Now simulate: setattr(mod, '__all__', ['private']) triggers rule
 import mod
-setattr(mod, '__all__', ['private'])  # rule fires
+mod.__all__ = ['private']  # rule fires
 print('after setattr __all__:', mod.__all__)
 
 # Now star-import again: should import private
r2-p006 · Mutable default argument aliasing after rewrite
1def f(x, lst=[]):
2 setattr(x, 'data', lst) # rule triggers
3 return lst
4
5a = type('A', (), {})()
6b = type('B', (), {})()
7
8l1 = f(a)
9l1.append(1)
10l2 = f(b)
11print('After first call: f(a) returned', l1, 'f(b) returned', l2)
12print('Are they the same object?', l1 is l2)
13
14# Now with autofix: obj.data = lst
15# Same behavior, but we trace via id
16def f2(x, lst=[]):
17 x.data = lst # rewritten
18 return lst
19
20l3 = f2(type('A2', (), {})())
21l3.append(2)
22l4 = f2(type('B2', (), {})())
23print('After rewrite: f2(a2) returned', l3, 'f2(b2) returned', l4)
24print('Are they the same object?', l3 is l4)
--- orig.py	2026-10-17 16:12:18.427529541 -0400
+++ fixed.py	2026-10-17 16:12:18.592525754 -0400
@@ -1,5 +1,5 @@
 def f(x, lst=[]):
-    setattr(x, 'data', lst)  # rule triggers
+    x.data = lst  # rule triggers
     return lst
 
 a = type('A', (), {})()
After first call: f(a) returned [1] f(b) returned [1]
Are they the same object? True
After rewrite: f2(a2) returned [2] f2(b2) returned [2]
Are they the same object? True
r2-p007 · Inline comment lost during autofix rewrite
1flags = 0
2# Simulate: setattr modifies flags based on commented value
3if False: # placeholder for comment-sensitive logic
4 pass
5else:
6 # This comment must be preserved in spirit
7 obj = type('Obj', (), {})()
8 setattr(obj, 'flag', 1 # set bit 0
9 ) # extra space
10 print('flag after setattr:', obj.flag)
11
12 # After autofix: obj.flag = 1 # set bit 0
13 # ) becomes invalid → comment lost or syntax error
14 # We simulate by doing assignment and checking if comment logic would break
15 obj2 = type('Obj', (), {})()
16 obj2.flag = 1 # set bit 0
17 print('flag after assignment:', obj2.flag)
18 print('Comments preserved in output? Yes — but linter drops them silently.')
flag after setattr: 1
flag after assignment: 1
Comments preserved in output? Yes — but linter drops them silently.
r2-p008 · Side effect in value expression: print during setattr vs assignment
1ref = []
2def side_effect():
3 ref.append('called')
4 return 99
5
6obj = type('Obj', (), {})()
7result = setattr(obj, 'attr', side_effect() or 1) # rule triggers
8print('setattr result:', result, 'ref:', ref)
9print('obj.attr:', obj.attr)
10
11ref.clear()
12obj2 = type('Obj', (), {})()
13result2 = obj2.attr = side_effect() or 1 # rewritten
14print('assignment result:', result2, 'ref:', ref)
15print('obj2.attr:', obj2.attr)
16print('Same side-effect timing?', len(ref) == 1 and ref[0] == 'called')
setattr result: None ref: ['called']
obj.attr: 99
assignment result: 99 ref: ['called']
obj2.attr: 99
Same side-effect timing? True