fixhunt

ruff FURB154

labelled unknown
Runs / 2026-10-17T21-33-02-131Z-n23d-ruff-FURB154ruff 0.17.12026-10-17 21:33 UTC8 programs × 2 round(s)16 programs, 16 fired, 16 fixed, 0 findings, 0 rejectedmodel cost $0.0032

Analysis Nemotron 3 Super

Flags consecutive 'global' or 'nonlocal' statements declaring separate variables when they could be combined into a single statement.

Fix: Replaces consecutive 'global' or 'nonlocal' statements with a single statement that combines all declared variables into a comma-separated list, preserving the order of appearance.

8 hazards
  • Moving a 'global' statement after a comment would detach the comment from the statement, altering readability and potentially meaning if the comment was intended as documentation for that specific declaration.
  • If a 'global' statement is followed by an inline comment on the same line, the fix would drop that comment because it only preserves comments on separate lines before the first statement.
  • When 'global' and 'nonlocal' statements are interleaved, combining them into one statement is invalid syntax because 'global' and 'nonlocal' cannot be mixed in the same statement.
  • If a variable name appears in both a 'global' and a subsequent 'nonlocal' statement (or vice versa), combining them would create a conflicting declaration that is invalid in Python scope rules.
  • Placing a combined 'global' statement before other statements that use those variables could change behavior if the original separate statements were guarded by a conditional that executed later, though this is prevented by the rule requiring consecutive statements.
  • If the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire function scope regardless of position.
  • In rare cases where a 'global' statement is used to override a built-in and the order of overriding matters for side effects in imported modules, combining statements does not change the order since all declarations happen simultaneously at function entry.
  • If a 'global' statement is split across multiple lines using backslash continuation, the fix would not apply because the rule only detects simple statement lines, so no hazard arises from the fix itself in such cases.

Programs

idresulttitlehazard
r1-p001fix okEvaluation Order Hazardevaluation order and side effects when expressions are merged, duplicated or deleted
r1-p002fix okParameter Defaults Hazardkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r1-p003fix ok prediction offBackslash Continuation Near-MissIf a 'global' statement is split across multiple lines using backslash continuation, the fix would not apply because the rule only detects simple statement lines, so no hazard arises from the fix itse
r1-p004fix okException Handling Hazardexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p005fix okAsync Generator Hazardasync def/await ordering, async generators
r1-p006fix okFile Write Hazardfile modes and truncation, partial writes, non-str data passed to write
r1-p007fix okNested Function Scope HazardIf the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire
r1-p008fix okBuilt-in Override HazardIn rare cases where a 'global' statement is used to override a built-in and the order of overriding matters for side effects in imported modules, combining statements does not change the order since a
r2-p001fix okString Formatting Edge Casesstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r2-p002fix okKeyword-only, Positional-only, Args/Kwargs Forwardingkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r2-p003fix okFile Modes and Truncationfile modes and truncation, partial writes, non-str data passed to write
r2-p004fix okAsync Def/Await Orderingasync def/await ordering, async generators
r2-p005fix okMutable Default Arguments and Aliasingmutable default arguments and aliasing after a rewrite
r2-p006fix okNested Function References Same NameIf the block contains a nested function that references the same variable name, moving the declaration via combining statements does not affect scoping because 'global'/'nonlocal' apply to the entire
r2-p007fix okWalrus, Comprehensions Scoping, Late Bindingwalrus, comprehensions scoping, late binding in closures and lambdas
r2-p008fix okGenerators vs Lists, Iterators Consumed Oncegenerators vs lists, iterators consumed once, lazy vs eager evaluation

Sources

r1-p001 · Evaluation Order Hazard
1def f():
2 i = [0]
3 global a
4 global b
5 a = i[0]; i[0] += 1
6 b = i[0]; i[0] += 1
7 return a + b
8print(f())
--- orig.py	2026-10-17 17:33:10.316609135 -0400
+++ fixed.py	2026-10-17 17:33:10.475607036 -0400
@@ -1,7 +1,6 @@
 def f():
     i = [0]
-    global a
-    global b
+    global a, b
     a = i[0]; i[0] += 1
     b = i[0]; i[0] += 1
     return a + b
1
r1-p002 · Parameter Defaults Hazard
1defaults = []
2def f(x=defaults.append(1) or 2):
3 global a
4 global b
5 return x
6print(f())
7print(f())
8print(defaults)
--- orig.py	2026-10-17 17:33:10.589605531 -0400
+++ fixed.py	2026-10-17 17:33:10.762603247 -0400
@@ -1,7 +1,6 @@
 defaults = []
 def f(x=defaults.append(1) or 2):
-    global a
-    global b
+    global a, b
     return x
 print(f())
 print(f())
2
2
[1]
r1-p003 · Backslash Continuation Near-Miss
1def f():
2 global \
3 a
4 global \
5 b
6 a = 1
7 b = 2
8 return a + b
9print(f())
--- orig.py	2026-10-17 17:33:10.862601927 -0400
+++ fixed.py	2026-10-17 17:33:11.033599670 -0400
@@ -1,8 +1,5 @@
 def f():
-    global \
-        a
-    global \
-        b
+    global a, b
     a = 1
     b = 2
     return a + b
3
r1-p004 · Exception Handling Hazard
1def f():
2 global a
3 global b
4 try:
5 a = 1 / 0
6 except ZeroDivisionError as e:
7 b = 42
8 raise RuntimeError('failed') from e
9 else:
10 b = 99
11 finally:
12 a = -1
13try:
14 f()
15except RuntimeError as e:
16 print('caught:', type(e).__name__, str(e))
17 print('cause:', type(e.__cause__).__name__ if e.__cause__ else None)
18 print('a=', a, 'b=', b)
--- orig.py	2026-10-17 17:33:11.128598417 -0400
+++ fixed.py	2026-10-17 17:33:11.296596202 -0400
@@ -1,6 +1,5 @@
 def f():
-    global a
-    global b
+    global a, b
     try:
         a = 1 / 0
     except ZeroDivisionError as e:
caught: RuntimeError failed
cause: ZeroDivisionError
a= -1 b= 42
r1-p005 · Async Generator Hazard
1async def f():
2 global x
3 global y
4 x = 1
5 await asyncio.sleep(0)
6 y = 2
7 yield x + y
8import asyncio
9async def main():
10 gen = f()
11 val = await gen.__anext__()
12 print('value:', val)
13 print('x=', x, 'y=', y)
14asyncio.run(main())
--- orig.py	2026-10-17 17:33:11.399594843 -0400
+++ fixed.py	2026-10-17 17:33:11.703590835 -0400
@@ -1,6 +1,5 @@
 async def f():
-    global x
-    global y
+    global x, y
     x = 1
     await asyncio.sleep(0)
     y = 2
value: 3
x= 1 y= 2
r1-p006 · File Write Hazard
1def f():
2 global log
3 global out
4 log = open('log.txt', 'w')
5 out = open('out.txt', 'w')
6 log.write('start\n')
7 out.write(b'bad')
8 log.close()
9 out.close()
10try:
11 f()
12except Exception as e:
13 print(type(e).__name__, str(e))
14finally:
15 try:
16 print(open('log.txt').read().strip())
17 except:
18 print('log: <error>')
19 try:
20 print(open('out.txt', 'rb').read())
21 except:
22 print('out: <error>')
--- orig.py	2026-10-17 17:33:11.867588672 -0400
+++ fixed.py	2026-10-17 17:33:12.031586510 -0400
@@ -1,6 +1,5 @@
 def f():
-    global log
-    global out
+    global log, out
     log = open('log.txt', 'w')
     out = open('out.txt', 'w')
     log.write('start\n')
TypeError write() argument must be str, not bytes

b''
r1-p007 · Nested Function Scope Hazard
1def f():
2 global x
3 global y
4 def inner():
5 return x + y
6 x = 10
7 y = 20
8 return inner()
9print(f())
10print('x=', x, 'y=', y)
--- orig.py	2026-10-17 17:33:12.137585114 -0400
+++ fixed.py	2026-10-17 17:33:12.306582889 -0400
@@ -1,6 +1,5 @@
 def f():
-    global x
-    global y
+    global x, y
     def inner():
         return x + y
     x = 10
30
x= 10 y= 20
r1-p008 · Built-in Override Hazard
1import math
2original_len = len
3def f():
4 global len
5 global sum
6 len = lambda x: 99
7 sum = lambda x: 88
8 return len([1,2,3]) + sum([4,5])
9print(f())
10print('len overridden:', len == (lambda x: 99))
11print('sum overridden:', sum == (lambda x: 88))
12print('math.len unchanged:', math.len is original_len)
--- orig.py	2026-10-17 17:33:12.411581504 -0400
+++ fixed.py	2026-10-17 17:33:12.807576290 -0400
@@ -1,8 +1,7 @@
 import math
 original_len = len
 def f():
-    global len
-    global sum
+    global len, sum
     len = lambda x: 99
     sum = lambda x: 88
     return len([1,2,3]) + sum([4,5])
187
len overridden: False
sum overridden: False
r2-p001 · String Formatting Edge Cases
1def f():
2 global a
3 global b
4 s = f'{a} {b}'
5 print(s)
6a = 1
7b = 2
8f()
--- orig.py	2026-10-17 17:33:17.690512217 -0400
+++ fixed.py	2026-10-17 17:33:17.857510033 -0400
@@ -1,6 +1,5 @@
 def f():
-    global a
-    global b
+    global a, b
     s = f'{a} {b}'
     print(s)
 a = 1
1 2
r2-p002 · Keyword-only, Positional-only, Args/Kwargs Forwarding
1def f(a, /, b, *, c=[]):
2 global d
3 global e
4 return a + b + len(c) + d + e
5d = 10
6e = 20
7print(f(1, 2, c=[3]))
--- orig.py	2026-10-17 17:33:17.958508711 -0400
+++ fixed.py	2026-10-17 17:33:18.129506476 -0400
@@ -1,6 +1,5 @@
 def f(a, /, b, *, c=[]):
-    global d
-    global e
+    global d, e
     return a + b + len(c) + d + e
 d = 10
 e = 20
34
r2-p003 · File Modes and Truncation
1def f():
2 global log
3 global out
4 log.write('start')
5 out.write(b'data')
6try:
7 with open('tmp.txt', 'w') as log, open('tmp.bin', 'wb') as out:
8 f()
9except Exception as ex:
10 print(type(ex).__name__, ex)
11finally:
12 import os
13 try: os.remove('tmp.txt'); os.remove('tmp.bin')
14 except: pass
--- orig.py	2026-10-17 17:33:18.224505235 -0400
+++ fixed.py	2026-10-17 17:33:18.396502987 -0400
@@ -1,6 +1,5 @@
 def f():
-    global log
-    global out
+    global log, out
     log.write('start')
     out.write(b'data')
 try:
r2-p004 · Async Def/Await Ordering
1async def f():
2 global x
3 global y
4 await asyncio.sleep(0)
5 return x + y
6x = 1
7y = 2
8import asyncio
9print(asyncio.run(f()))
--- orig.py	2026-10-17 17:33:18.493501720 -0400
+++ fixed.py	2026-10-17 17:33:18.801497696 -0400
@@ -1,6 +1,5 @@
 async def f():
-    global x
-    global y
+    global x, y
     await asyncio.sleep(0)
     return x + y
 x = 1
3
r2-p005 · Mutable Default Arguments and Aliasing
1def f(lst=[]):
2 global a
3 global b
4 lst.append(a)
5 lst.append(b)
6 return lst
7a = 10
8b = 20
9print(f())
10print(f())
--- orig.py	2026-10-17 17:33:18.975495422 -0400
+++ fixed.py	2026-10-17 17:33:19.140493268 -0400
@@ -1,6 +1,5 @@
 def f(lst=[]):
-    global a
-    global b
+    global a, b
     lst.append(a)
     lst.append(b)
     return lst
[10, 20]
[10, 20, 10, 20]
r2-p006 · Nested Function References Same Name
1def f():
2 global x
3 global y
4 def inner():
5 return x + y
6 x = 1
7 y = 2
8 return inner()
9x = 0
10y = 0
11print(f())
--- orig.py	2026-10-17 17:33:19.244491911 -0400
+++ fixed.py	2026-10-17 17:33:19.399489889 -0400
@@ -1,6 +1,5 @@
 def f():
-    global x
-    global y
+    global x, y
     def inner():
         return x + y
     x = 1
3
r2-p007 · Walrus, Comprehensions Scoping, Late Binding
1def f():
2 global a
3 global b
4 funcs = [(lambda: a + b) for _ in range(2) if (tmp := a) is not None]
5 a = 10
6 b = 20
7 return [g() for g in funcs]
8a = 0
9b = 0
10print(f())
--- orig.py	2026-10-17 17:33:19.506488492 -0400
+++ fixed.py	2026-10-17 17:33:19.664486431 -0400
@@ -1,6 +1,5 @@
 def f():
-    global a
-    global b
+    global a, b
     funcs = [(lambda: a + b) for _ in range(2) if (tmp := a) is not None]
     a = 10
     b = 20
[30, 30]
r2-p008 · Generators vs Lists, Iterators Consumed Once
1def f():
2 global data
3 global idx
4 data = [1, 2, 3]
5 idx = 0
6 def gen():
7 while idx < len(data):
8 yield data[idx]
9 idx += 1
10 return gen()
11g = f()
12print(list(g))
13print(list(g))
--- orig.py	2026-10-17 17:33:19.768485074 -0400
+++ fixed.py	2026-10-17 17:33:20.152480065 -0400
@@ -1,6 +1,5 @@
 def f():
-    global data
-    global idx
+    global data, idx
     data = [1, 2, 3]
     idx = 0
     def gen():