fixhunt

ruff UP039

labelled unknown
Runs / 2026-10-17T21-25-09-748Z-iww3-ruff-UP039ruff 0.17.12026-10-17 21:25 UTC8 programs × 2 round(s)16 programs, 14 fired, 14 fixed, 0 findings, 1 rejectedmodel cost $0.0029

Analysis Nemotron 3 Super

Flags class definitions with empty parentheses when no base classes are specified.

Fix: Removes the empty parentheses following the class name when no base classes are present, preserving all other tokens and whitespace outside the parentheses range.

10 hazards
  • The rewrite may remove inline comments inside the parentheses, such as `class A(): # comment` becoming `class A: # comment`, altering comment placement relative to the class header.
  • If the parentheses contain a trailing comma like `class A(),`, the fix would incorrectly produce `class A:,` which is a syntax error due to dangling comma.
  • In Python 2, empty parentheses in class definitions could influence metaclass resolution implicitly; removing them might alter behavior in legacy code relying on old-style class semantics.
  • The fix does not account for parentheses used in type hints on the class line (e.g., `class A() -> None:`), where removing them breaks the return annotation syntax.
  • If the class definition is part of a larger statement via line continuation (e.g., `class A() \ : pass`), the fix may break syntactic structure by removing parentheses needed for line joining.
  • When the class header includes decorators (e.g., `@dec\nclass A():`), the fix operates correctly but may be misapplied if parentheses are mistakenly interpreted as part of the decorator expression.
  • The fix preserves surrounding whitespace but may alter formatting if the parentheses contained internal whitespace that was significant for alignment or readability in a team's style guide.
  • In code generated by tools or macros that rely on the exact token sequence `class A():` for pattern matching or replacement, the rewrite could break tooling expectations.
  • If the class is defined in a string or eval context (e.g., `eval('class A(): pass')`), the fix does not apply, but if applied manually, it would change the string content and thus the evaluated behavior.
  • The rewrite assumes the parentheses are solely for base classes; however, in rare cases involving experimental syntax or future-language parsers, empty parentheses might be reserved for other meanings, making removal forward-incompatible.

Programs

idresulttitlehazard
r1-p001fix okKeyword-only, positional-only, args/kwargs, defaultsEmpty parentheses after class name define default argument evaluation scope; removing them changes when mutable defaults are evaluated.
r1-p002fix okExceptions raised inside rewritten code, finally/else, chainingClass body execution affects exception state; removing parentheses may alter __init_subclass__ timing and exception propagation.
r1-p003fix okType annotations evaluated at runtime, typing-only importsAnnotated class variables with forward references rely on __annotations__ dict; class body execution order affects availability.
r1-p004fix okGenerators vs lists, iterators consumed once, lazy/eagerClass body contains a generator expression; removing parentheses changes when it's evaluated (eager vs lazy) and whether it's exhausted.
r1-p005fix okClass attributes vs instance attributes, descriptors, propertiesProperty with side effect in class body; removal changes when property is accessed during class creation vs instantiation.
r1-p006fix okPathlib vs os.path behavior on relative paths, symlinks, missing filesClass attribute uses Path() touching filesystem; removal changes when path is resolved (import time vs runtime).
r1-p007fix okContext managers that are not files, objects with write() but no close()Class body uses a custom context manager with side effects; removal changes when __enter__/__exit__ run.
r1-p008fix ok prediction offRewrite assumes parentheses are solely for base classes; rare experimental/future syntaxEmpty parentheses might be reserved for future syntax (e.g., class parameters); removing them breaks forward compatibility.
r2-p001fix okCoercion with Decimalint/float/bool coercion, Decimal, division and rounding
r2-p002fix okStar Import Side Effectsstar-imports, module-level side effects, __all__
r2-p003silentLegacy Metaclass ResolutionIn Python 2, empty parentheses in class definitions could influence metaclass resolution implicitly; removing them might alter behavior in legacy code relying on old-style class semantics.
r2-p004fix okEvaluation Order Merged Expressionsevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p005invalidType Hint Return AnnotationThe fix does not account for parentheses used in type hints on the class line (e.g., `class A() -> None:`), where removing them breaks the return annotation syntax.
r2-p006fix okFuture Syntax ReserveThe rewrite assumes the parentheses are solely for base classes; however, in rare cases involving experimental syntax or future-language parsers, empty parentheses might be reserved for other meanings
r2-p007fix okTooling Token MatchIn code generated by tools or macros that rely on the exact token sequence `class A():` for pattern matching or replacement, the rewrite could break tooling expectations.
r2-p008fix okFile Write Truncation Timingfile modes and truncation, partial writes, non-str data passed to write

Sources

r1-p001 · Keyword-only, positional-only, args/kwargs, defaults
1def f(x=[]):
2 x.append(1)
3 return x
4class A():
5 b = f()
6print(A.b)
7A2 = A()
8print(f())
--- orig.py	2026-10-17 17:25:19.136502057 -0400
+++ fixed.py	2026-10-17 17:25:19.309497464 -0400
@@ -1,7 +1,7 @@
 def f(x=[]):
     x.append(1)
     return x
-class A():
+class A:
     b = f()
 print(A.b)
 A2 = A()
[1]
[1, 1]
r1-p002 · Exceptions raised inside rewritten code, finally/else, chaining
1class Base:
2 def __init_subclass__(cls):
3 raise ValueError('bad')
4try:
5 class C():
6 pass
7except ValueError as e:
8 print('caught:', e)
9else:
10 print('no ex')
11finally:
12 print('finally')
--- orig.py	2026-10-17 17:25:19.413494704 -0400
+++ fixed.py	2026-10-17 17:25:19.573490456 -0400
@@ -2,7 +2,7 @@
     def __init_subclass__(cls):
         raise ValueError('bad')
 try:
-    class C():
+    class C:
         pass
 except ValueError as e:
     print('caught:', e)
no ex
finally
r1-p003 · Type annotations evaluated at runtime, typing-only imports
1class A():
2 x: 'B' = None
3class B: pass
4print(A.__annotations__['x'])
5print(B)
--- orig.py	2026-10-17 17:25:19.682487562 -0400
+++ fixed.py	2026-10-17 17:25:19.859482864 -0400
@@ -1,4 +1,4 @@
-class A():
+class A:
     x: 'B' = None
 class B: pass
 print(A.__annotations__['x'])
B
<class '__main__.B'>
r1-p004 · Generators vs lists, iterators consumed once, lazy/eager
1gen = (i for i in [1,2,3])
2class A():
3 y = list(gen)
4print('gen exhausted?', list(gen))
5print('A.y:', A.y)
--- orig.py	2026-10-17 17:25:19.971479890 -0400
+++ fixed.py	2026-10-17 17:25:20.136475516 -0400
@@ -1,5 +1,5 @@
 gen = (i for i in [1,2,3])
-class A():
+class A:
     y = list(gen)
 print('gen exhausted?', list(gen))
 print('A.y:', A.y)
gen exhausted? []
A.y: [1, 2, 3]
r1-p005 · Class attributes vs instance attributes, descriptors, properties
1class A():
2 @property
3 def x(self):
4 print('prop accessed')
5 return 1
6print('before inst')
7a = A()
8print(a.x)
--- orig.py	2026-10-17 17:25:20.231472999 -0400
+++ fixed.py	2026-10-17 17:25:20.405468387 -0400
@@ -1,4 +1,4 @@
-class A():
+class A:
     @property
     def x(self):
         print('prop accessed')
before inst
prop accessed
1
r1-p006 · Pathlib vs os.path behavior on relative paths, symlinks, missing files
1import pathlib
2class A():
3 p = pathlib.Path('tmp.txt')
4 p.write_text('hi')
5print('exists:', A.p.exists())
6print('content:', A.p.read_text())
7A.p.unlink()
--- orig.py	2026-10-17 17:25:20.509465631 -0400
+++ fixed.py	2026-10-17 17:25:20.709460330 -0400
@@ -1,5 +1,5 @@
 import pathlib
-class A():
+class A:
     p = pathlib.Path('tmp.txt')
     p.write_text('hi')
 print('exists:', A.p.exists())
exists: True
content: hi
r1-p007 · Context managers that are not files, objects with write() but no close()
1class CM:
2 def __enter__(self):
3 print('enter')
4 return self
5 def __exit__(self, *exc):
6 print('exit')
7class A():
8 with CM():
9 pass
10print('done')
--- orig.py	2026-10-17 17:25:20.834457017 -0400
+++ fixed.py	2026-10-17 17:25:20.986452988 -0400
@@ -4,7 +4,7 @@
         return self
     def __exit__(self, *exc):
         print('exit')
-class A():
+class A:
     with CM():
         pass
 print('done')
enter
exit
done
r1-p008 · Rewrite assumes parentheses are solely for base classes; rare experimental/future syntax
1# This is a placeholder for future syntax
2# Currently, class A(): is valid and equivalent to class A:
3# If future Python uses () for class params, removing them would break
4# For now, we show equivalence
5class A():
6 x = 1
7class B:
8 x = 1
9print('A.x:', A.x)
10print('B.x:', B.x)
11print('Equal:', A.x == B.x)
--- orig.py	2026-10-17 17:25:21.089450262 -0400
+++ fixed.py	2026-10-17 17:25:21.254445896 -0400
@@ -2,7 +2,7 @@
 # Currently, class A(): is valid and equivalent to class A:
 # If future Python uses () for class params, removing them would break
 # For now, we show equivalence
-class A():
+class A:
     x = 1
 class B:
     x = 1
A.x: 1
B.x: 1
Equal: True
r2-p001 · Coercion with Decimal
1class X():
2 a = float(int(Decimal('5')) // 2)
3
4from decimal import Decimal
5print(X.a)
6del X
--- orig.py	2026-10-17 17:25:25.623330658 -0400
+++ fixed.py	2026-10-17 17:25:26.059319199 -0400
@@ -1,4 +1,4 @@
-class X():
+class X:
     a = float(int(Decimal('5')) // 2)
 
 from decimal import Decimal
r2-p002 · Star Import Side Effects
1__all__ = ['flag']
2flag = 0
3
4class Y():
5 global flag
6 flag += 1
7
8from __main__ import *
9print(flag)
--- orig.py	2026-10-17 17:25:26.279313425 -0400
+++ fixed.py	2026-10-17 17:25:26.448308989 -0400
@@ -1,7 +1,7 @@
 __all__ = ['flag']
 flag = 0
 
-class Y():
+class Y:
     global flag
     flag += 1
 
1
r2-p003 · Legacy Metaclass Resolution
1# Hazard refers to Python 2 behavior; this version intentionally avoids triggering the rule.
2# Using explicit inheritance to avoid being a candidate for UP039 while still illustrating concept.
3try:
4 class Z(object): pass
5 print(type(Z))
6except Exception as e:
7 print(f'{type(e).__name__}: {e}')
<class 'type'>
r2-p004 · Evaluation Order Merged Expressions
1seq = []
2def log(v): seq.append(v); return v
3
4class A():
5 x = log(1)
6 y = log(2)
7
8print(seq)
9print(A.x, A.y)
10del A
--- orig.py	2026-10-17 17:25:26.682302848 -0400
+++ fixed.py	2026-10-17 17:25:26.846298544 -0400
@@ -1,7 +1,7 @@
 seq = []
 def log(v): seq.append(v); return v
 
-class A():
+class A:
     x = log(1)
     y = log(2)
 
[1, 2]
1 2
r2-p005 · Type Hint Return Annotation (generated program does not parse: Traceback (most recent call last):)
1class B() -> str:
2 return 'test'
3
4print(B.__annotations__)
5try:
6 B()
7except TypeError as e:
8 print(f'{type(e).__name__}: {e}')
9del B
r2-p006 · Future Syntax Reserve
1# Placeholder for potential future syntax: class params
2# Though not valid now, simulating token sensitivity
3class C():
4 __params__ = ()
5
6print(hasattr(C, '__params__'))
7del C
--- orig.py	2026-10-17 17:25:27.085292275 -0400
+++ fixed.py	2026-10-17 17:25:27.252287899 -0400
@@ -1,6 +1,6 @@
 # Placeholder for potential future syntax: class params
 # Though not valid now, simulating token sensitivity
-class C():
+class C:
     __params__ = ()
 
 print(hasattr(C, '__params__'))
True
r2-p007 · Tooling Token Match
1token_seq = 'class D():\n x = 1'
2source = ''
3class D():
4 x = 1
5 source = token_seq
6
7print(source == token_seq)
8del D
--- orig.py	2026-10-17 17:25:27.353285252 -0400
+++ fixed.py	2026-10-17 17:25:27.521280850 -0400
@@ -1,6 +1,6 @@
 token_seq = 'class D():\n    x = 1'
 source = ''
-class D():
+class D:
     x = 1
     source = token_seq
 
False
r2-p008 · File Write Truncation Timing
1class E():
2 f = open('tmp.txt', 'w')
3 f.write('early')
4 f.close()
5
6with open('tmp.txt', 'r') as f:
7 print(f.read())
8import os; os.remove('tmp.txt')
9del E
--- orig.py	2026-10-17 17:25:27.616278361 -0400
+++ fixed.py	2026-10-17 17:25:27.778274116 -0400
@@ -1,4 +1,4 @@
-class E():
+class E:
     f = open('tmp.txt', 'w')
     f.write('early')
     f.close()
early