fixhunt

ruff UP051

labelled unknown
Runs / 2026-10-17T21-27-48-158Z-zpxr-ruff-UP051ruff 0.17.12026-10-17 21:27 UTC8 programs × 2 round(s)16 programs, 5 fired, 5 fixed, 0 findings, 8 rejectedmodel cost $0.0046

Analysis Nemotron 3 Super

Flags usage of deprecated abstract base class decorators `@abc.abstractclassmethod`, `@abc.abstractstaticmethod`, and `@abc.abstractproperty`.

Fix: Replaces `@abc.abstractclassmethod` with `@classmethod` followed by `@abc.abstractmethod`, `@abc.abstractstaticmethod` with `@staticmethod` followed by `@abc.abstractmethod`, and `@abc.abstractproperty` with `@property` followed by `@abc.abstractmethod`, preserving decorator order where applicable.

10 hazards
  • Rewriting `@abc.abstractproperty` to `@property\n@abc.abstractmethod` changes evaluation order if other decorators are present above, potentially altering wrapper semantics.
  • If `@abc.abstractclassmethod` is used with a metaclass that intercepts `__new__`, replacing it may alter class creation behavior due to differing descriptor protocol timing.
  • The fix may duplicate existing `@classmethod` or `@staticmethod` decorators when they already precede the deprecated decorator, resulting in redundant or conflicting decorator stacks.
  • Comments placed between the deprecated decorator and the function definition may be lost or misattributed during rewrite if the tool does not preserve inter-decorator formatting.
  • In code using string annotations (`from __future__ import annotations`), moving decorators could theoretically affect runtime evaluation if decorators depend on annotation resolution—though unlikely, it introduces a theoretical hazard.
  • If `@abc.abstractproperty` is applied to a non-method (e.g., a module-level variable), the rewrite to `@property` will fail at runtime because `@property` requires a getter method.
  • When the deprecated decorator is used in a dynamically generated class via `type()`, the rewrite may not preserve the intended decoration order if the autofix assumes static structure.
  • If an alias like `ac = abc.abstractclassmethod` is used, the rule may not fire (due to missing name match), but if it did fire incorrectly, the fix would produce invalid syntax by inserting `@classmethod` and `@abc.abstractmethod` without context.
  • Applying the fix to nested functions inside other functions may break scoping if the autofix incorrectly lifts or reorders decorators relative to `nonlocal` or `global` statements.
  • In code that relies on the deprecated decorator’s specific `__isabstractmethod__` behavior (now emulated), subtle differences in introspection could affect frameworks that inspect ABCs directly.

Programs

idresulttitlehazard
r1-p001invalidEvaluation Order HazardSide effects in expression merging during rewrite can alter evaluation sequence.
r1-p002invalidAsync Def/Await OrderingRewriting @abc.abstractclassmethod on async methods disrupts await/order semantics.
r1-p003silentAbstract Property on Variable@property rewrite fails when applied to non-method (module-level variable).
r1-p004invalidAlias Decorator MisfireIncorrect fix on aliased decorator produces invalid syntax due to missing context.
r1-p005invalidComments and DocstringsComments/docstrings on removed decorator are lost or misplaced after rewrite.
r1-p006invalidExceptions and Finally ClausesExceptions in rewritten methods interact incorrectly with finally/else due to wrapper order.
r1-p007invalidFile Modes and TruncationSide effects in abstract method (e.g., file write) are altered by wrapper order in fix.
r1-p008invalidIsabstractmethod IntrospectionRewrite changes __isabstractmethod__ behavior, affecting framework introspection.
r2-p001fix okEvaluation Order Hazard - Abstract Property RewriteRewriting `@abc.abstractproperty` to `@property\n@abc.abstractmethod` changes evaluation order when other decorators are present above.
r2-p002silentAlias Decorator Misfire - Abstract Classmethod AliasIf an alias like `ac = abc.abstractclassmethod` is used, incorrect fix would produce invalid syntax by inserting decorators without context.
r2-p003fix okWalrus and Comprehension Scoping HazardWalrus operator, comprehensions scoping, late binding in closures and lambdas affected by decorator rewrite order.
r2-p004fix okFile Modes and Truncation HazardSide effects in abstract method (e.g., file write) altered by wrapper order after rewrite of @abc.abstractproperty.
r2-p005silent prediction offDynamic Class Creation via type() HazardWhen deprecated decorator used in dynamically generated class via `type()`, rewrite may not preserve decoration order.
r2-p006fix okMutable Default Arguments and Aliasing HazardMutable default arguments and aliasing after rewrite cause shared state across calls.
r2-p007invalidString Annotations Evaluation Order HazardIn code using string annotations, moving decorators could theoretically affect runtime evaluation if decorators depend on annotation resolution.
r2-p008fix okTruthiness and Identity Comparison HazardTruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' affected by decorator rewrite altering descriptor behavior.

Sources

r1-p001 · Evaluation Order Hazard (generated program does not parse: Traceback (most recent call last):)
1import abc
2counter = [0]
3def inc():
4 counter[0] += 1
5 return counter[0]
6@abc.abstractclassmethod
7 def cls_meth(cls):
8 return inc() + inc()
9print('Result:', cls_meth.__func__(type('T', (), {})))
r1-p002 · Async Def/Await Ordering (generated program does not parse: Traceback (most recent call last):)
1import abc
2async def async_val():
3 return 42
4@abc.abstractclassmethod
5 async def async_cls_meth(cls):
6 x = await async_val()
7 y = await async_val()
8 return x + y
9obj = type('Obj', (), {})
10print('Async result:', asyncio.run(async_cls_meth.__func__(type(obj))))
r1-p003 · Abstract Property on Variable
1import abc
2value = 123
3try:
4 exec('@abc.abstractproperty\nvalue')
5except SyntaxError as e:
6 print('SyntaxError:', type(e).__name__, str(e))
SyntaxError: SyntaxError invalid syntax (<string>, line 2)
r1-p004 · Alias Decorator Misfire (generated program does not parse: Traceback (most recent call last):)
1import abc
2ac = abc.abstractclassmethod
3# Rule does not fire here; if it did incorrectly, fix would be invalid
4@ac
5 def fake_method(cls): pass
6# Simulate what incorrect fix would produce: @classmethod\n@abc.abstractmethod\n# This would be valid syntax but semantically wrong—we test validity via try
7try:
8 exec('@classmethod\n@abc.abstractmethod\ndef fake_method(cls): pass')
9 print('No syntax error from simulated fix')
10except SyntaxError as e:
11 print('SyntaxError from simulated fix:', type(e).__name__, str(e))
r1-p005 · Comments and Docstrings (generated program does not parse: Traceback (most recent call last):)
1import abc
2# This comment should stay
3@abc.abstractclassmethod
4 """This docstring should be preserved."""
5 def meth(cls):
6 return 'preserved'
7print('Docstring:', meth.__func__.__doc__)
8# Check if comment proximity affects anything—no runtime effect, but we trace via doc
r1-p006 · Exceptions and Finally Clauses (generated program does not parse: Traceback (most recent call last):)
1import abc
2log = []
3@abc.abstractclassmethod
4 def faulty(cls):
5 try:
6 log.append('try')
7 raise ValueError('error')
8 except ValueError as e:
9 log.append('except')
10 raise RuntimeError('chained') from e
11 else:
12 log.append('else')
13 finally:
14 log.append('finally')
15try:
16 faulty.__func__(type('T', (), {}))
17except RuntimeError as e:
18 print('Log:', log)
19 print('Exception:', type(e).__name__, str(e))
20 if e.__cause__:
21 print('Cause:', type(e.__cause__).__name__, str(e.__cause__))
r1-p007 · File Modes and Truncation (generated program does not parse: Traceback (most recent call last):)
1import abc
2import os
3fname = 'test_file.txt'
4if os.path.exists(fname):
5 os.remove(fname)
6@abc.abstractclassmethod
7 def writer(cls):
8 with open(fname, 'w') as f:
9 f.write('first')
10 with open(fname, 'a') as f:
11 f.write('second')
12writer.__func__(type('W', (), {}))
13with open(fname, 'r') as f:
14 content = f.read()
15print('File content:', repr(content))
16os.remove(fname)
r1-p008 · Isabstractmethod Introspection (generated program does not parse: Traceback (most recent call last):)
1import abc
2from inspect import isabstract
3@abc.abstractclassmethod
4 def amethod(cls): pass
5@classmethod
6@abc.abstractmethod
7 def bmethod(cls): pass
8print('amethod isabstract:', isabstract(amethod))
9print('bmethod isabstract:', isabstract(bmethod))
10print('amethod.__isabstractmethod__:', getattr(amethod, '__isabstractmethod__', 'missing'))
11print('bmethod.__isabstractmethod__:', getattr(bmethod, '__isabstractmethod__', 'missing'))
r2-p001 · Evaluation Order Hazard - Abstract Property Rewrite
1import abc
2log = []
3def make_dec(name):
4 log.append(f'dec_{name}')
5 def dec(f):
6 log.append(f'apply_{name}')
7 return f
8 return dec
9
10@make_dec('outer')
11@abc.abstractproperty
12@make_dec('inner')
13def prop(self):
14 log.append('prop_body')
15 return 42
16
17class C(abc.ABC):
18 pass
19C.prop = prop
20
21try:
22 c = C()
23 _ = c.prop # triggers descriptor
24 log.append('accessed')
25except Exception as e:
26 log.append(f'{type(e).__name__}:{e}')
27
28print('|'.join(log))
--- orig.py	2026-10-17 17:28:07.936573495 -0400
+++ fixed.py	2026-10-17 17:28:08.110569960 -0400
@@ -8,7 +8,8 @@
     return dec
 
 @make_dec('outer')
-@abc.abstractproperty
+@property
+@abc.abstractmethod
 @make_dec('inner')
 def prop(self):
     log.append('prop_body')
dec_outer|dec_inner|apply_inner|apply_outer|prop_body|accessed
r2-p002 · Alias Decorator Misfire - Abstract Classmethod Alias
1import abc
2ac = abc.abstractclassmethod # alias
3
4# This line should NOT trigger UP051 because ac is not abc.abstractclassmethod
5@ac
6def cls_method(cls):
7 return 'cls_result'
8
9# Verify it works as abstractclassmethod
10class Base(abc.ABC):
11 pass
12
13try:
14 Base.cls_method = cls_method
15 b = Base()
16 result = b.cls_method()
17 print(f'success:{result}')
18except Exception as e:
19 print(f'{type(e).__name__}:{e}')
success:cls_result
r2-p003 · Walrus and Comprehension Scoping Hazard
1import abc
2
3records = []
4
5@abc.abstractstaticmethod
6def make_counter():
7 n = 0
8 def counter():
9 nonlocal n
10 n += 1
11 records.append(('call', n))
12 return n
13 return counter
14
15# Rewrite should become @staticmethod\n@abc.abstractmethod
16counter_func = make_counter()
17
18class CounterBase(abc.ABC):
19 pass
20CounterBase.get_count = counter_func
21
22# Use walrus in comprehension
23results = [x for y in range(3) if (x := CounterBase().get_count()) > 0]
24
25# Late-binding lambda hazard
26funcs = []
27for i in range(2):
28 funcs.append(lambda: CounterBase().get_count() + i)
29
30try:
31 f1, f2 = funcs
32 late = f1() # should be 1 + 0 = 1 if i captured, else 1+1=2
33 records.append(('late', late))
34except Exception as e:
35 records.append(('error', str(e)))
36
37print(','.join(map(str, records)))
--- orig.py	2026-10-17 17:28:08.344565208 -0400
+++ fixed.py	2026-10-17 17:28:08.736557247 -0400
@@ -2,7 +2,8 @@
 
 records = []
 
-@abc.abstractstaticmethod
+@staticmethod
+@abc.abstractmethod
 def make_counter():
     n = 0
     def counter():
r2-p004 · File Modes and Truncation Hazard
1import abc
2
3class FileLogger(abc.ABC):
4 @abc.abstractproperty
5 def log_path(self):
6 'Should return path, but has side effect'
7
8# Simulate rewrite: @property\n@abc.abstractmethod
9# This changes when __set_name__ or descriptor setup happens
10
11path_written = []
12
13class ConcreteLogger(FileLogger):
14 def __init__(self):
15 self._path = '/tmp/test.txt'
16
17 @property
18 def log_path(self):
19 # Side effect: truncate file on access
20 open(self._path, 'w').close()
21 path_written.append(self._path)
22 return self._path
23
24logger = ConcreteLogger()
25try:
26 _ = logger.log_path # first access truncates
27 with open(logger.log_path, 'a') as f:
28 f.write('data')
29 _ = logger.log_path # second access truncates again!
30 with open(logger.log_path) as f:
31 content = f.read()
32 print(f'truncates:{len(path_written)} content:{repr(content)}')
33except Exception as e:
34 print(f'{type(e).__name__}:{e}')
--- orig.py	2026-10-17 17:28:08.944553023 -0400
+++ fixed.py	2026-10-17 17:28:09.104549777 -0400
@@ -1,7 +1,8 @@
 import abc
 
 class FileLogger(abc.ABC):
-    @abc.abstractproperty
+    @property
+    @abc.abstractmethod
     def log_path(self):
         'Should return path, but has side effect'
 
truncates:4 content:''
r2-p005 · Dynamic Class Creation via type() Hazard
1import abc
2
3decorator_log = []
4
5def logged_abstractclassmethod(func):
6 decorator_log.append('wrap_acm')
7 return abc.abstractclassmethod(func)
8
9# Build class dynamically
10attrs = {
11 '__module__': '__main__',
12 'dynamic_method': logged_abstractclassmethod(lambda cls: 'dynamic')
13}
14
15DynamicCls = type('DynamicCls', (abc.ABC,), attrs)
16
17# Now simulate what autofix would do: replace with @classmethod + @abstractmethod
18# But order matters: if abstractmethod goes first, it breaks
19try:
20 obj = DynamicCls()
21 result = obj.dynamic_method()
22 decorator_log.append(f'result:{result}')
23except Exception as e:
24 decorator_log.append(f'{type(e).__name__}:{e}')
25
26print('|'.join(decorator_log))
wrap_acm|TypeError:Can't instantiate abstract class DynamicCls without an implementation for abstract method 'dynamic_method'
r2-p006 · Mutable Default Arguments and Aliasing Hazard
1import abc
2
3registry = []
4
5@abc.abstractstaticmethod
6def register(item, storage=[]):
7 storage.append(item)
8 registry.append(tuple(storage)) # snapshot
9 return storage
10
11# After rewrite: @staticmethod\n@abc.abstractmethod
12class Storage(abc.ABC):
13 pass
14Storage.register = register
15
16try:
17 s = Storage()
18 s.register('a') # storage=['a']
19 s.register('b') # storage=['a','b'] due to mutable default
20 s.register('c') # storage=['a','b','c']
21 print(f'registry:{len(registry)} last:{registry[-1] if registry else []}')
22except Exception as e:
23 print(f'{type(e).__name__}:{e}')
--- orig.py	2026-10-17 17:28:09.339545011 -0400
+++ fixed.py	2026-10-17 17:28:09.500541747 -0400
@@ -2,7 +2,8 @@
 
 registry = []
 
-@abc.abstractstaticmethod
+@staticmethod
+@abc.abstractmethod
 def register(item, storage=[]):
     storage.append(item)
     registry.append(tuple(storage))  # snapshot
registry:3 last:('a', 'b', 'c')
r2-p007 · String Annotations Evaluation Order Hazard (generated program does not parse: Traceback (most recent call last):)
1from __future__ import annotations
2import abc
3
4 eval_order = []
5
6def annotate_side_effect():
7 eval_order.append('annotated')
8 return str
9
10@abc.abstractproperty
11def annotated_prop(self) -> 'annotate_side_effect()': # string ann
12 eval_order.append('prop_body')
13 return 'value'
14
15# After rewrite: @property\n@abc.abstractmethod
16class PropHolder(abc.ABC):
17 pass
18PropHolder.annotated_prop = annotated_prop
19
20try:
21 obj = PropHolder()
22 # Access triggers descriptor; annotation not evaluated at call time
23 # but if decorator order affected __set_name__ timing...
24 val = obj.annotated_prop
25 eval_order.append(f'accessed:{val}')
26except Exception as e:
27 eval_order.append(f'{type(e).__name__}:{e}')
28
29print('|'.join(eval_order))
r2-p008 · Truthiness and Identity Comparison Hazard
1import abc
2
3class BoolTracker:
4 instances = []
5 def __init__(self, val):
6 self.val = val
7 BoolTracker.instances.append(self)
8 def __bool__(self):
9 return bool(self.val)
10 def __len__(self):
11 return len(str(self.val))
12
13@abc.abstractproperty
14def truth_value(self):
15 BoolTracker.accessed = getattr(BoolTracker, 'accessed', 0) + 1
16 return BoolTracker(self.val) if hasattr(self, 'val') else BoolTracker(None)
17
18class ValueHolder(abc.ABC):
19 pass
20ValueHolder.truth_value = truth_value
21
22# Test truthiness
23obj = ValueHolder()
24obj.val = [] # falsy
25
26try:
27 prop = obj.truth_value # returns BoolTracker instance
28 truth = bool(prop)
29 length = len(prop)
30 is_none = prop is None
31 eq_none = prop == None
32 accessed = getattr(BoolTracker, 'accessed', 0)
33 print(f'truth:{truth} len:{length} isNone:{is_none} eqNone:{eq_none} accessed:{accessed}')
34except Exception as e:
35 print(f'{type(e).__name__}:{e}')
--- orig.py	2026-10-17 17:28:09.737536941 -0400
+++ fixed.py	2026-10-17 17:28:09.898533677 -0400
@@ -10,7 +10,8 @@
     def __len__(self):
         return len(str(self.val))
 
-@abc.abstractproperty
+@property
+@abc.abstractmethod
 def truth_value(self):
     BoolTracker.accessed = getattr(BoolTracker, 'accessed', 0) + 1
     return BoolTracker(self.val) if hasattr(self, 'val') else BoolTracker(None)
truth:False len:2 isNone:False eqNone:False accessed:1