fixhunt

ruff PLR0203

labelled unknown
Runs / 2026-10-17T21-00-16-738Z-3bi8-ruff-PLR0203ruff 0.17.12026-10-17 21:00 UTC8 programs × 2 round(s)16 programs, 11 fired, 11 fixed, 0 findings, 4 rejectedmodel cost $0.0038

Analysis Nemotron 3 Super

Flags assignments where a function defined earlier in the same class body is reassigned via `staticmethod(func)` as a method replacement.

Fix: Replace the assignment `name = staticmethod(name)` with the decorator `@staticmethod` placed directly above the function definition of `name`, preserving the function's name, parameters, body, and decorators, and removing the reassignment line.

11 hazards
  • If the function name is used in an annotation within its own body (e.g., `def f(x: f) -> int:`), moving the decorator above may cause a NameError because `f` is not yet defined at annotation evaluation time.
  • If the function defines a nested function that references the outer function's name via closure (e.g., `def f(): def g(): return f`), the rewrite breaks the closure because the original function object is replaced by a staticmethod wrapper before the nested function is defined.
  • If the function is referenced in a default argument of a later-defined function in the same class body (e.g., `def f(): pass def g(x=f): pass`), the rewrite changes when the staticmethod wrapper is applied, potentially altering the value captured in the default if the wrapper has side effects (though it doesn't, the timing of binding changes relative to other class body statements).
  • If the assignment line carries a comment (e.g., `f = staticmethod(f) # workaround`), the rewrite drops that comment entirely, as the fix only preserves comments attached to the function definition.
  • If the function is decorated with other decorators (e.g., `@property def f(): pass f = staticmethod(f)`), the rewrite incorrectly places `@staticmethod` above `@property`, reversing decorator order and changing semantics (property vs staticmethod).
  • If the function name is deleted after the assignment (e.g., `f = staticmethod(f); del f`), the rewrite leaves the function accessible via its original name, whereas the original code intended to remove it after wrapping.
  • If the function is aliased before reassignment (e.g., `orig = f; f = staticmethod(f)`), the rewrite does not update the alias `orig`, which remains bound to the unwrapped function, diverging from the original behavior where `orig` and `f` both pointed to the same unwrapped function before reassignment.
  • If the function definition contains a `yield` expression and is used in a generator expression within its own defaults (e.g., `def f(x=(yield 42)): pass; f = staticmethod(f)`), the rewrite changes the timing of when the staticmethod wrapper is applied relative to the first invocation, affecting generator state initialization.
  • If the class is defined inside a function or loop and the staticmethod assignment relies on the enclosing scope for nonlocal variables accessed in the function body, the rewrite is safe but may interact poorly with tools that expect the decorator form for static analysis.
  • If the function name is the same as a class variable or imported name in the class scope (e.g., `f = 1 def f(): pass f = staticmethod(f)`), the rewrite may confuse linters or readers about which `f` is being decorated, though runtime behavior is preserved.
  • If the assignment uses a qualified name like `C.f = staticmethod(f)` inside the class body (invalid syntax but sometimes seen in generated code), the fix does not apply and the rule may not fire, but if it did, the rewrite would be incorrect.

Programs

idresulttitlehazard
r1-p001fix okClosure capture broken by premature staticmethod wrappingIf the function defines a nested function that references the outer function's name via closure (e.g., `def f(): def g(): return f`), the rewrite breaks the closure because the original function objec
r1-p002invalidName shadowing confusion between class var and functionIf the function name is the same as a class variable or imported name in the class scope (e.g., `f = 1\ndef f(): pass\nf = staticmethod(f)`), the rewrite may confuse linters or readers about which `f`
r1-p003fix okFile write with partial data and non-str input after staticmethod rewritefile modes and truncation, partial writes, non-str data passed to write
r1-p004invalidProperty descriptor side effects confused with staticmethodclass attributes vs instance attributes, descriptors, properties with side effects
r1-p005invalidWalrus and late binding in comprehension capturing reassigned namewalrus, comprehensions scoping, late binding in closures and lambdas
r1-p006fix okType annotation evaluated at runtime referencing reassigned functiontype annotations evaluated at runtime, typing-only imports
r1-p007fix okException in finally block after staticmethod rewriteexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p008fix okKeyword-only args with default evaluated once, affected by reassignment timingkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r2-p001fix okAnnotation self-reference causes NameError after fixIf the function name is used in an annotation within its own body (e.g., `def f(x: f) -> int:`), moving the decorator above may cause a NameError because `f` is not yet defined at annotation evaluatio
r2-p002fix okDefault argument captures pre-wrapper function due to timing shiftIf the function is referenced in a default argument of a later-defined function in the same class body (e.g., `def f(): pass def g(x=f): pass`), the rewrite changes when the staticmethod wrapper is ap
r2-p003fix okContext manager-like object with write but no close used in function bodycontext managers that are not files, objects with write() but no close()
r2-p004invalidDecorator order reversed when combining @property and staticmethod reassignmentIf the function is decorated with other decorators (e.g., `@property def f(): pass f = staticmethod(f)`), the rewrite incorrectly places `@staticmethod` above `@property`, reversing decorator order
r2-p005silentQualified assignment C.f = staticmethod(f) not handled by fixIf the assignment uses a qualified name like `C.f = staticmethod(f)` inside the class body (invalid syntax but sometimes seen in generated code), the fix does not apply and the rule may not fire, but
r2-p006fix okNested function accesses nonlocal from enclosing function scopeIf the class is defined inside a function or loop and the staticmethod assignment relies on the enclosing scope for nonlocal variables accessed in the function body, the rewrite is safe but may intera
r2-p007fix okAlias to function remains bound to original after reassignmentIf the function is aliased before reassignment (e.g., `orig = f; f = staticmethod(f)`), the rewrite does not update the alias `orig`, which remains bound to the unwrapped function, diverging from the
r2-p008fix okFunction return value used in truthiness context after reassignmenttruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='

Sources

r1-p001 · Closure capture broken by premature staticmethod wrapping
1class C:
2 def f():
3 def g():
4 return f
5 return g
6 f = staticmethod(f)
7
8c = C()
9g = c.f()
10print(type(g()))
--- orig.py	2026-10-17 17:00:30.979846091 -0400
+++ fixed.py	2026-10-17 17:00:31.379851074 -0400
@@ -1,9 +1,9 @@
 class C:
+    @staticmethod
     def f():
         def g():
             return f
         return g
-    f = staticmethod(f)
 
 c = C()
 g = c.f()
r1-p002 · Name shadowing confusion between class var and function (generated program is not deterministic)
1class C:
2 f = 99
3 def f():
4 return 'function'
5 f = staticmethod(f)
6
7print(C.f)
8print(C.f())
r1-p003 · File write with partial data and non-str input after staticmethod rewrite
1class C:
2 def log(msg):
3 with open('test.txt', 'w') as fp:
4 fp.write(msg) # expects str
5 fp.write(b'\n') # error: bytes not str
6 log = staticmethod(log)
7
8try:
9 C.log('hello')
10except Exception as e:
11 print(type(e).__name__, str(e))
12finally:
13 import os
14 if os.path.exists('test.txt'):
15 os.remove('test.txt')
--- orig.py	2026-10-17 17:00:31.684854873 -0400
+++ fixed.py	2026-10-17 17:00:31.853856978 -0400
@@ -1,9 +1,9 @@
 class C:
+    @staticmethod
     def log(msg):
         with open('test.txt', 'w') as fp:
             fp.write(msg)  # expects str
             fp.write(b'\n')  # error: bytes not str
-    log = staticmethod(log)
 
 try:
     C.log('hello')
TypeError write() argument must be str, not bytes
r1-p004 · Property descriptor side effects confused with staticmethod (generated program is not deterministic)
1class C:
2 _val = 0
3 @property
4 def score(self):
5 C._val += 1
6 return C._val
7 score = staticmethod(score) # rule triggers: replaces property with staticmethod
8
9c = C()
10try:
11 print(c.score)
12except Exception as e:
13 print(type(e).__name__, str(e))
r1-p005 · Walrus and late binding in comprehension capturing reassigned name (generated program is not deterministic)
1class C:
2 def make():
3 return [lambda: f for _ in range(1) if (f := lambda: 42) is not None]
4 make = staticmethod(make)
5
6funcs = C.make()
7print(funcs[0]())
r1-p006 · Type annotation evaluated at runtime referencing reassigned function
1class C:
2 def f() -> 'return_f()':
3 pass
4 def return_f():
5 return f
6 f = staticmethod(f)
7
8try:
9 C.f()
10except Exception as e:
11 print(type(e).__name__, str(e))
--- orig.py	2026-10-17 17:00:32.143860589 -0400
+++ fixed.py	2026-10-17 17:00:32.309862656 -0400
@@ -1,9 +1,9 @@
 class C:
+    @staticmethod
     def f() -> 'return_f()':
         pass
     def return_f():
         return f
-    f = staticmethod(f)
 
 try:
     C.f()
r1-p007 · Exception in finally block after staticmethod rewrite
1class C:
2 def fails():
3 try:
4 raise ValueError('bad')
5 finally:
6 raise RuntimeError('worse')
7 fails = staticmethod(fails)
8
9try:
10 C.fails()
11except Exception as e:
12 print(type(e).__name__, str(e))
--- orig.py	2026-10-17 17:00:32.417864001 -0400
+++ fixed.py	2026-10-17 17:00:32.583866067 -0400
@@ -1,10 +1,10 @@
 class C:
+    @staticmethod
     def fails():
         try:
             raise ValueError('bad')
         finally:
             raise RuntimeError('worse')
-    fails = staticmethod(fails)
 
 try:
     C.fails()
RuntimeError worse
r1-p008 · Keyword-only args with default evaluated once, affected by reassignment timing
1class C:
2 default = []
3 def append_val(x, *, lst=default):
4 lst.append(x)
5 return lst
6 append_val = staticmethod(append_val)
7
8print(C.append_val(1))
9print(C.append_val(2))
--- orig.py	2026-10-17 17:00:32.689867387 -0400
+++ fixed.py	2026-10-17 17:00:32.847869355 -0400
@@ -1,9 +1,9 @@
 class C:
     default = []
+    @staticmethod
     def append_val(x, *, lst=default):
         lst.append(x)
         return lst
-    append_val = staticmethod(append_val)
 
 print(C.append_val(1))
 print(C.append_val(2))
[1]
[1, 2]
r2-p001 · Annotation self-reference causes NameError after fix
1class C:
2 def f(x: f) -> int:
3 return x
4 f = staticmethod(f)
5try:
6 c = C()
7 print(c.f(1))
8except Exception as e:
9 print(type(e).__name__, str(e))
--- orig.py	2026-10-17 17:00:38.834943776 -0400
+++ fixed.py	2026-10-17 17:00:39.012945986 -0400
@@ -1,7 +1,7 @@
 class C:
+    @staticmethod
     def f(x: f) -> int:
         return x
-    f = staticmethod(f)
 try:
     c = C()
     print(c.f(1))
1
r2-p002 · Default argument captures pre-wrapper function due to timing shift
1class C:
2 def f():
3 return 9
4 def g(x=f):
5 return x()
6 f = staticmethod(f)
7print(C().g())
--- orig.py	2026-10-17 17:00:39.119947314 -0400
+++ fixed.py	2026-10-17 17:00:39.556952737 -0400
@@ -1,7 +1,7 @@
 class C:
+    @staticmethod
     def f():
         return 9
     def g(x=f):
         return x()
-    f = staticmethod(f)
 print(C().g())
r2-p003 · Context manager-like object with write but no close used in function body
1class Writer:
2 def write(self, s):
3 print(s, end='')
4class C:
5 def log(msg):
6 w = Writer()
7 w.write(msg)
8 log = staticmethod(log)
9C.log('hello')
10print('done')
--- orig.py	2026-10-17 17:00:39.777955479 -0400
+++ fixed.py	2026-10-17 17:00:39.946957576 -0400
@@ -2,9 +2,9 @@
     def write(self, s):
         print(s, end='')
 class C:
+    @staticmethod
     def log(msg):
         w = Writer()
         w.write(msg)
-    log = staticmethod(log)
 C.log('hello')
 print('done')
hellodone
r2-p004 · Decorator order reversed when combining @property and staticmethod reassignment (generated program is not deterministic)
1class C:
2 @property
3 def f():
4 return 42
5 f = staticmethod(f)
6try:
7 print(C().f)
8except Exception as e:
9 print(type(e).__name__, str(e))
r2-p005 · Qualified assignment C.f = staticmethod(f) not handled by fix
1class C:
2 def f():
3 return 1
4 # This line is invalid syntax; we cannot execute it.
5 # Instead, we show that the rule does not trigger on valid code.
6 pass
7print('no trigger')
no trigger
r2-p006 · Nested function accesses nonlocal from enclosing function scope
1def make_class():
2 x = 10
3 class C:
4 def get():
5 nonlocal x
6 return x
7 get = staticmethod(get)
8 return C
9C = make_class()
10print(C().get())
--- orig.py	2026-10-17 17:00:40.264961521 -0400
+++ fixed.py	2026-10-17 17:00:40.429963567 -0400
@@ -1,10 +1,10 @@
 def make_class():
     x = 10
     class C:
+        @staticmethod
         def get():
             nonlocal x
             return x
-        get = staticmethod(get)
     return C
 C = make_class()
 print(C().get())
10
r2-p007 · Alias to function remains bound to original after reassignment
1class C:
2 def f():
3 return 'original'
4 orig = f
5 f = staticmethod(f)
6print('orig:', orig())
7print('f:', C().f())
8print('same?', orig is C.__dict__['f'])
--- orig.py	2026-10-17 17:00:40.526964770 -0400
+++ fixed.py	2026-10-17 17:00:40.921969669 -0400
@@ -1,8 +1,8 @@
 class C:
+    @staticmethod
     def f():
         return 'original'
     orig = f
-    f = staticmethod(f)
 print('orig:', orig())
 print('f:', C().f())
 print('same?', orig is C.__dict__['f'])
r2-p008 · Function return value used in truthiness context after reassignment
1class C:
2 def f():
3 return []
4 f = staticmethod(f)
5result = C().f()
6if result:
7 print('truthy')
8else:
9 print('falsy')
10print('len:', len(result))
--- orig.py	2026-10-17 17:00:41.132972286 -0400
+++ fixed.py	2026-10-17 17:00:41.299974356 -0400
@@ -1,7 +1,7 @@
 class C:
+    @staticmethod
     def f():
         return []
-    f = staticmethod(f)
 result = C().f()
 if result:
     print('truthy')
falsy
len: 0