fixhunt

ruff PLR0202

labelled unknown
Runs / 2026-10-17T20-59-52-966Z-ny2q-ruff-PLR0202ruff 0.17.12026-10-17 20:59 UTC8 programs × 2 round(s)16 programs, 15 fired, 15 fixed, 0 findings, 0 rejectedmodel cost $0.0035

Analysis Nemotron 3 Super

Flags methods defined as plain functions then assigned via `classmethod(func)` outside the function body.

Fix: Replaces the method definition and subsequent assignment with a single method definition prefixed by the `@classmethod` decorator, preserving the original function name, parameters, and body.

12 hazards
  • If the method name is used in an annotation within the same class body before the assignment, moving the decorator may change the binding time and cause a NameError.
  • If the class contains a `__prepare__` metaclass method that relies on the order of attribute definitions, inserting the decorator earlier may alter the namespace preparation state.
  • If the method is decorated with other decorators (e.g., `@property`) in the original code, the autofix does not preserve them, potentially changing the descriptor behavior.
  • If the method assignment is guarded by a runtime condition (e.g., inside an `if` block), the autofix hoists it to unconditional execution, altering when the method becomes a classmethod.
  • If the method name is deleted (`del m`) after the assignment but before the end of the class body, the autofix incorrectly retains the method as a classmethod.
  • If the method is assigned to an alias (e.g., `n = m`) after the `classmethod` assignment, the autofix breaks the alias because `n` refers to the undecorated function in the original but would refer to the classmethod-descriptor after the fix.
  • If the method definition contains a type comment (e.g., `# type: (cls) -> int`) placed after the function, the autofix may discard it during rewrite, altering static type checking behavior.
  • If the method is defined in a class executed under `-OO` (optimized mode) where docstrings are omitted, the autofix preserves the docstring but the rewrite timing may interact unexpectedly with stripped constants.
  • If the class is nested inside a function and the method uses a closure variable from the outer scope, the autofix does not affect closure capture, but the redefinition order may interfere with late-bound defaults in rare cases.
  • If the method name is `__classcell__` (used in Python 3.7+ for class cell handling), rewriting it as a decorated method may interfere with the class creation protocol.
  • If the method assignment is preceded by a `from __future__ import` statement that affects execution semantics, the autofix does not account for potential changes in how the function is compiled.
  • If the method is part of a mixin intended to be overridden and the assignment relies on the original function being temporarily accessible via the class dict, the autofix may break intermediate access patterns during class construction.

Programs

idresulttitlehazard
r1-p001fix ok__prepare__ metaclass depends on attribute definition orderInserting @classmethod earlier alters namespace preparation state during class creation.
r1-p002fix okAlias to method breaks after autofixAlias `n = m` after `classmethod` assignment refers to raw function; after fix refers to descriptor.
r1-p003fix okWalrus and closure capture with late bindingLambda capturing method via walrus in comprehension binds late; autofix changes timing of descriptor availability.
r1-p004fix ok-OO mode interacts with docstring preservationAutofix preserves docstring but -OO strips constants; rewrite may keep dead string affecting memory or timing.
r1-p005fix okGenerator expression consumes iterator onceMethod used in generator expression; autofix changes when descriptor is available, affecting lazy evaluation.
r1-p006fix okFile write with truncation and partial writeMethod used in file-writing loop; autofix changes timing, risking incomplete write if interrupted.
r1-p007fix okStar-import and __all__ interactionMethod assigned to __all__; autofix may alter when symbol is available for export.
r1-p008fix okfrom __future__ import affects function compilationFuture import (e.g., annotations) changes function semantics; autofix doesn't reapply context.
r2-p001fix okAnnotation forward referenceIf the method name is used in an annotation within the same class body before the assignment, moving the decorator may change the binding time and cause a NameError.
r2-p002fix okBytes vs str encodingbytes vs str, encoding and newline handling, text vs binary modes
r2-p003silent prediction offConditional assignment hoistIf the method assignment is guarded by a runtime condition (e.g., inside an `if` block), the autofix hoists it to unconditional execution, altering when the method becomes a classmethod.
r2-p004fix ok-OO mode docstring interactionIf the method is defined in a class executed under `-OO` (optimized mode) where docstrings are omitted, the autofix preserves the docstring but the rewrite timing may interact unexpectedly with stripp
r2-p005fix okOther decorators lostIf the method is decorated with other decorators (e.g., `@property`) in the original code, the autofix does not preserve them, potentially changing the descriptor behavior.
r2-p006fix okNested class closure captureIf the class is nested inside a function and the method uses a closure variable from the outer scope, the autofix does not affect closure capture, but the redefinition order may interfere with late-bo
r2-p007fix okDict ordering duplicate keysdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r2-p008fix okMutable default aliasingmutable default arguments and aliasing after a rewrite

Sources

r1-p001 · __prepare__ metaclass depends on attribute definition order
1class Meta(type):
2 def __prepare__(name, bases):
3 return {}
4 def __new__(cls, name, bases, ns):
5 order = [k for k in ('a', 'm') if k in ns]
6 ns['order'] = tuple(order)
7 return super().__new__(cls, name, bases, ns)
8
9class C(metaclass=Meta):
10 def m(cls): return 'm'
11 m = classmethod(m)
12 def a(cls): return 'a'
13
14print(C.order)
--- orig.py	2026-10-17 17:00:05.813530631 -0400
+++ fixed.py	2026-10-17 17:00:05.976532687 -0400
@@ -7,8 +7,8 @@
         return super().__new__(cls, name, bases, ns)
 
 class C(metaclass=Meta):
+    @classmethod
     def m(cls): return 'm'
-    m = classmethod(m)
     def a(cls): return 'a'
 
 print(C.order)
('a', 'm')
r1-p002 · Alias to method breaks after autofix
1class C:
2 def m(cls): return 42
3 m = classmethod(m)
4 n = m # alias to original function
5
6try:
7 print('n:', n) # should be function
8 print('n():', n()) # fails: missing cls
9 print('C.m():', C.m()) # works: 42
10 print('C.n():', C.n()) # after fix: would fail if n is descriptor
11
12except Exception as e:
13 print(type(e).__name__, ':', e)
--- orig.py	2026-10-17 17:00:06.074533923 -0400
+++ fixed.py	2026-10-17 17:00:06.236535965 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls): return 42
-    m = classmethod(m)
     n = m  # alias to original function
 
 try:
NameError : name 'n' is not defined
r1-p003 · Walrus and closure capture with late binding
1class C:
2 def m(cls): return cls.__name__
3 m = classmethod(m)
4 fs = [(lambda: m()) for _ in range(1)]
5
6print('fs[0]():', fs[0]()) # Should be 'C'
7# After fix: if m is descriptor at lambda definition, still works
8# But hazard: if class var lookup changes due to timing
--- orig.py	2026-10-17 17:00:06.331537163 -0400
+++ fixed.py	2026-10-17 17:00:06.712541967 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls): return cls.__name__
-    m = classmethod(m)
     fs = [(lambda: m()) for _ in range(1)]
 
 print('fs[0]():', fs[0]())  # Should be 'C'
r1-p004 · -OO mode interacts with docstring preservation
1class C:
2 def m(cls):
3 '''This is a docstring'''
4 return 42
5 m = classmethod(m)
6
7print('C.m.__doc__:', repr(C.m.__doc__))
8print('C.m():', C.m())
--- orig.py	2026-10-17 17:00:06.936544791 -0400
+++ fixed.py	2026-10-17 17:00:07.103546896 -0400
@@ -1,8 +1,8 @@
 class C:
+    @classmethod
     def m(cls):
         '''This is a docstring'''
         return 42
-    m = classmethod(m)
 
 print('C.m.__doc__:', repr(C.m.__doc__))
 print('C.m():', C.m())
C.m.__doc__: 'This is a docstring'
C.m(): 42
r1-p005 · Generator expression consumes iterator once
1class C:
2 def m(cls): return iter([1, 2, 3])
3 m = classmethod(m)
4 g = (next(m()) for _ in range(3))
5
6print('list(g):', list(g)) # Should be [1, 2, 3]
7# After fix: if m is descriptor earlier, same result
8# Hazard: if m looked up too early and state changed
--- orig.py	2026-10-17 17:00:07.206548194 -0400
+++ fixed.py	2026-10-17 17:00:07.593553071 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls): return iter([1, 2, 3])
-    m = classmethod(m)
     g = (next(m()) for _ in range(3))
 
 print('list(g):', list(g))  # Should be [1, 2, 3]
r1-p006 · File write with truncation and partial write
1class C:
2 def m(cls): return b'hello'
3 m = classmethod(m)
4
5with open('test.bin', 'wb') as f:
6 for i in range(2):
7 f.write(m()) # writes b'hello' twice
8
9with open('test.bin', 'rb') as f:
10 print('file contents:', f.read())
11import os; os.remove('test.bin')
--- orig.py	2026-10-17 17:00:07.807555767 -0400
+++ fixed.py	2026-10-17 17:00:08.196560668 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls): return b'hello'
-    m = classmethod(m)
 
 with open('test.bin', 'wb') as f:
     for i in range(2):
r1-p007 · Star-import and __all__ interaction
1class C:
2 def m(cls): return 'exported'
3 m = classmethod(m)
4 __all__ = ['m']
5
6# Simulate star import: check what's in __all__
7print('C.__all__:', C.__all__)
8print('hasattr C.m:', hasattr(C, 'm'))
9print('C.m():', C.m())
--- orig.py	2026-10-17 17:00:08.417563452 -0400
+++ fixed.py	2026-10-17 17:00:08.584565555 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls): return 'exported'
-    m = classmethod(m)
     __all__ = ['m']
 
 # Simulate star import: check what's in __all__
C.__all__: ['m']
hasattr C.m: True
C.m(): exported
r1-p008 · from __future__ import affects function compilation
1from __future__ import annotations
2
3class C:
4 def m(cls) -> 'int': # string annotation due to future import
5 return 42
6 m = classmethod(m)
7
8print('C.m.__annotations__:', C.m.__annotations__)
9print('C.m():', C.m())
--- orig.py	2026-10-17 17:00:08.691566903 -0400
+++ fixed.py	2026-10-17 17:00:08.857568994 -0400
@@ -1,9 +1,9 @@
 from __future__ import annotations
 
 class C:
+    @classmethod
     def m(cls) -> 'int':  # string annotation due to future import
         return 42
-    m = classmethod(m)
 
 print('C.m.__annotations__:', C.m.__annotations__)
 print('C.m():', C.m())
C.m.__annotations__: {'return': "'int'"}
C.m(): 42
r2-p001 · Annotation forward reference
1class C:
2 f: 'm'
3 def m(cls):
4 return 42
5 m = classmethod(m)
6print(C.f.__class__)
--- orig.py	2026-10-17 17:00:14.005633744 -0400
+++ fixed.py	2026-10-17 17:00:14.421638968 -0400
@@ -1,6 +1,6 @@
 class C:
     f: 'm'
+    @classmethod
     def m(cls):
         return 42
-    m = classmethod(m)
 print(C.f.__class__)
r2-p002 · Bytes vs str encoding
1class C:
2 def m(cls):
3 return b'data'
4 m = classmethod(m)
5with open('tmp.bin', 'wb') as f:
6 f.write(C.m())
7with open('tmp.bin', 'rb') as f:
8 print(f.read())
9import os; os.remove('tmp.bin')
--- orig.py	2026-10-17 17:00:14.635641655 -0400
+++ fixed.py	2026-10-17 17:00:14.799643715 -0400
@@ -1,7 +1,7 @@
 class C:
+    @classmethod
     def m(cls):
         return b'data'
-    m = classmethod(m)
 with open('tmp.bin', 'wb') as f:
     f.write(C.m())
 with open('tmp.bin', 'rb') as f:
b'data'
r2-p003 · Conditional assignment hoist
1flag = True
2class C:
3 def m(cls):
4 return 'ok'
5 if flag:
6 m = classmethod(m)
7print(type(C.m))
<class 'method'>
r2-p004 · -OO mode docstring interaction
1class C:
2 def m(cls):
3 """doc"""
4 return __doc__
5 m = classmethod(m)
6print(C.m())
--- orig.py	2026-10-17 17:00:15.050646867 -0400
+++ fixed.py	2026-10-17 17:00:15.203648787 -0400
@@ -1,6 +1,6 @@
 class C:
+    @classmethod
     def m(cls):
         """doc"""
         return __doc__
-    m = classmethod(m)
 print(C.m())
None
r2-p005 · Other decorators lost
1class C:
2 @property
3 def m(cls):
4 return 'prop'
5 m = classmethod(m)
6try:
7 print(C.m)
8except Exception as e:
9 print(type(e).__name__, e)
--- orig.py	2026-10-17 17:00:15.304650055 -0400
+++ fixed.py	2026-10-17 17:00:15.467652101 -0400
@@ -1,8 +1,8 @@
 class C:
+    @classmethod
     @property
     def m(cls):
         return 'prop'
-    m = classmethod(m)
 try:
     print(C.m)
 except Exception as e:
<bound method m of <class '__main__.C'>>
r2-p006 · Nested class closure capture
1def make():
2 x = 99
3 class C:
4 def m(cls):
5 return x
6 m = classmethod(m)
7 return C
8C = make()
9print(C.m())
--- orig.py	2026-10-17 17:00:15.562653294 -0400
+++ fixed.py	2026-10-17 17:00:15.723655315 -0400
@@ -1,9 +1,9 @@
 def make():
     x = 99
     class C:
+        @classmethod
         def m(cls):
             return x
-        m = classmethod(m)
     return C
 C = make()
 print(C.m())
99
r2-p007 · Dict ordering duplicate keys
1class C:
2 def m(cls):
3 return {}
4 m = classmethod(m)
5d = {'a': 1, 'b': 2}
6d[C.m()] = 3
7print(d.get('a'), d.get('b'))
--- orig.py	2026-10-17 17:00:15.826656608 -0400
+++ fixed.py	2026-10-17 17:00:16.223661590 -0400
@@ -1,7 +1,7 @@
 class C:
+    @classmethod
     def m(cls):
         return {}
-    m = classmethod(m)
 d = {'a': 1, 'b': 2}
 d[C.m()] = 3
 print(d.get('a'), d.get('b'))
r2-p008 · Mutable default aliasing
1class C:
2 def m(cls, lst=[]):
3 lst.append(1)
4 return lst
5 m = classmethod(m)
6print(C.m())
7print(C.m())
--- orig.py	2026-10-17 17:00:16.430664187 -0400
+++ fixed.py	2026-10-17 17:00:16.580666069 -0400
@@ -1,7 +1,7 @@
 class C:
+    @classmethod
     def m(cls, lst=[]):
         lst.append(1)
         return lst
-    m = classmethod(m)
 print(C.m())
 print(C.m())
[1]
[1, 1]