fixhunt

ruff RET502

labelled unknown
Runs / 2026-10-17T20-31-43-992Z-tn0e-ruff-RET502ruff 0.17.12026-10-17 20:31 UTC8 programs × 2 round(s)16 programs, 13 fired, 13 fixed, 0 findings, 2 rejectedmodel cost $0.0045

Analysis Nemotron 3 Super

Flags bare 'return' statements in functions where at least one other return returns a non-None value.

Fix: Replace each bare 'return' statement with 'return None'.

10 hazards
  • Rewriting 'return' inside a generator function changes it from yielding nothing to yielding None, altering iterator output.
  • In async functions, 'return' becomes 'return None', which may affect awaited values if callers expect no return value.
  • If the bare 'return' is inside a nested function that is used as a context manager, returning None instead of implicitly returning None has no behavioral difference but could confuse static analysis tools expecting generator-like behavior.
  • If the function's return annotation is '-> None', adding explicit 'return None' is redundant but harmless; however, if annotation is missing or incorrect, it may mask type checker warnings about inconsistent return types.
  • If the bare 'return' occurs after a side-effecting expression (e.g., 'print(x); return'), moving 'None' into the return statement does not change side effects, but if the rewrite were mistakenly placed before the side effect due to incorrect line wrapping, it would alter execution order.
  • If the code contains a trailing comment on the same line as 'return' (e.g., 'return # early exit'), the rewrite may drop the comment if the tool does not preserve comments during token replacement.
  • If the bare 'return' is inside a lambda (though lambdas cannot contain bare returns), this shape cannot occur, but if mistakenly applied via macro-generated code, it would raise a SyntaxError.
  • In functions decorated with @property, @staticmethod, or @classmethod, the rewrite behaves identically since these decorators do not alter return value semantics.
  • If the function is overridden in a subclass and the bare 'return' is part of a contract expecting implicit None, making it explicit may improve clarity but does not change behavior.
  • If the code relies on the disassembly showing 'LOAD_CONST(None)' vs. implicit return, debugging or profiling tools that distinguish explicit vs. implicit returns may report different bytecode, though behavior is identical.

Programs

idresulttitlehazard
r1-p001invalidLambda syntax errorBare return inside lambda raises SyntaxError; fixing it makes invalid code valid.
r1-p002fix okContext manager confusionRewriting bare return to return None in __exit__ doesn't suppress exceptions but changes attribute visibility.
r1-p003fix okFinally clause interactionException handling chain altered by explicit return vs implicit flow through finally.
r1-p004fix okMutable default interferenceRewrite affects when mutation occurs relative to early exit; list persistence changed.
r1-p005invalidShadowed built-in clashLocal return override interacts with later global reassignment affecting closure capture.
r1-p006fix okWalrus and comprehension scopeEarly return in nested scope alters walrus variable persistence in enclosing comprehension.
r1-p007fix okParameter forwarding complexityExplicit return None interferes with *args/**kwargs debugging introspection vs implicit.
r1-p008fix okSubclass override clarityExplicit return None in base class method makes override intent clearer without behavior change.
r2-p001fix okProperty decorator - return rewrite under @propertyIn functions decorated with @property, @staticmethod, or @classmethod, the rewrite behaves identically since these decorators do not alter return value semantics.
r2-p002fix okRuntime type annotations with typing importstype annotations evaluated at runtime, typing-only imports
r2-p003fix okAsync function - return None affects awaited valueIn async functions, 'return' becomes 'return None', which may affect awaited values if callers expect no return value.
r2-p004fix okStar-imports and module-level side effectsstar-imports, module-level side effects, __all__
r2-p005fix okCustom __bool__ and None vs falsy comparisontruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='
r2-p006fix okNested function used as context managerIf the bare 'return' is inside a nested function that is used as a context manager, returning None instead of implicitly returning None has no behavioral difference but could confuse static analysis t
r2-p007fix okComments and docstrings on rewritten returncomments and docstrings attached to the removed or moved code
r2-p008silent prediction offFile write with non-str data and truncationfile modes and truncation, partial writes, non-str data passed to write

Sources

r1-p001 · Lambda syntax error (generated program does not parse: Traceback (most recent call last):)
1def outer():
2 # This line is syntactically invalid; included to illustrate impossible scenario
3 lam = lambda: (yield) or return
4 return lam
r1-p002 · Context manager confusion
1class CM:
2 def __enter__(self): return self
3 def __exit__(self, exc_type, exc, tb):
4 if exc_type is ValueError:
5 return
6 return False
7with CM() as cm:
8 raise ValueError('oops')
9print(cm.__dict__.get('suppressed', 'no'))
--- orig.py	2026-10-17 16:31:58.308675715 -0400
+++ fixed.py	2026-10-17 16:31:58.685676138 -0400
@@ -2,7 +2,7 @@
     def __enter__(self): return self
     def __exit__(self, exc_type, exc, tb):
         if exc_type is ValueError:
-            return
+            return None
         return False
 with CM() as cm:
     raise ValueError('oops')
r1-p003 · Finally clause interaction
1def f(x):
2 try:
3 if x == 0:
4 return 1/x
5 return
6 except ZeroDivisionError as e:
7 print('caught:', e)
8 return -1
9 finally:
10 print('finally')
11print(f(0))
12print(f(1))
--- orig.py	2026-10-17 16:31:58.907676387 -0400
+++ fixed.py	2026-10-17 16:31:59.070676569 -0400
@@ -2,7 +2,7 @@
     try:
         if x == 0:
             return 1/x
-        return
+        return None
     except ZeroDivisionError as e:
         print('caught:', e)
         return -1
caught: division by zero
finally
-1
finally
None
r1-p004 · Mutable default interference
1log = []
2def append_if_nonzero(n, log=log):
3 if n <= 0:
4 return
5 log.append(n)
6 return log
7append_if_nonzero(0)
8append_if_nonzero(1)
9print(log)
--- orig.py	2026-10-17 16:31:59.172676684 -0400
+++ fixed.py	2026-10-17 16:31:59.333676865 -0400
@@ -1,7 +1,7 @@
 log = []
 def append_if_nonzero(n, log=log):
     if n <= 0:
-        return
+        return None
     log.append(n)
     return log
 append_if_nonzero(0)
[1]
r1-p005 · Shadowed built-in clash (generated program does not parse: Traceback (most recent call last):)
1def make_func():
2 return = 42 # shadows built-in
3 def inner():
4 if True:
5 return return
6 return None
7 return inner
8f = make_func()
9del return # remove shadow
10print(f())
r1-p006 · Walrus and comprehension scope
1def gen_vals():
2 vals = []
3 for i in range(3):
4 if (x := i) == 1:
5 return
6 vals.append(x*2)
7 return vals
8print(gen_vals())
--- orig.py	2026-10-17 16:31:59.587677151 -0400
+++ fixed.py	2026-10-17 16:31:59.747677331 -0400
@@ -2,7 +2,7 @@
     vals = []
     for i in range(3):
         if (x := i) == 1:
-            return
+            return None
         vals.append(x*2)
     return vals
 print(gen_vals())
None
r1-p007 · Parameter forwarding complexity
1def wrapper(*args, **kwargs):
2 if not args:
3 return
4 return inner(*args, **kwargs)
5def inner(a, b=10):
6 print(a, b)
7 return a + b
8wrapper()
9wrapper(5)
--- orig.py	2026-10-17 16:31:59.847677444 -0400
+++ fixed.py	2026-10-17 16:32:00.017677635 -0400
@@ -1,6 +1,6 @@
 def wrapper(*args, **kwargs):
     if not args:
-        return
+        return None
     return inner(*args, **kwargs)
 def inner(a, b=10):
     print(a, b)
5 10
r1-p008 · Subclass override clarity
1class Base:
2 def process(self):
3 if not self.enabled:
4 return
5 return 'work'
6class Derived(Base):
7 def __init__(self):
8 self.enabled = False
9 def process(self):
10 super().process()
11 return 'overridden'
12d = Derived()
13print(d.process() or 'None')
--- orig.py	2026-10-17 16:32:00.116677747 -0400
+++ fixed.py	2026-10-17 16:32:00.285677938 -0400
@@ -1,7 +1,7 @@
 class Base:
     def process(self):
         if not self.enabled:
-            return
+            return None
         return 'work'
 class Derived(Base):
     def __init__(self):
overridden
r2-p001 · Property decorator - return rewrite under @property
1class C:
2 @property
3 def val(self):
4 if self._x > 0:
5 return
6 return self._x
7
8 def __init__(self, x):
9 self._x = x
10
11c = C(5)
12print(c.val)
13c._x = -1
14print(c.val)
--- orig.py	2026-10-17 16:32:05.449683840 -0400
+++ fixed.py	2026-10-17 16:32:05.620684037 -0400
@@ -2,7 +2,7 @@
     @property
     def val(self):
         if self._x > 0:
-            return
+            return None
         return self._x
 
     def __init__(self, x):
None
-1
r2-p002 · Runtime type annotations with typing imports
1from typing import List, Optional
2
3def f(x: Optional[int]) -> List[int]:
4 if x is None:
5 return
6 return [x] * 2
7
8print(f(None))
9print(f(3))
--- orig.py	2026-10-17 16:32:05.722684155 -0400
+++ fixed.py	2026-10-17 16:32:05.902684363 -0400
@@ -2,7 +2,7 @@
 
 def f(x: Optional[int]) -> List[int]:
     if x is None:
-        return
+        return None
     return [x] * 2
 
 print(f(None))
None
[3, 3]
r2-p003 · Async function - return None affects awaited value
1async def fetch(flag):
2 if not flag:
3 return
4 return 42
5
6async def main():
7 result = await fetch(False)
8 print('result:', result)
9 result2 = await fetch(True)
10 print('result2:', result2)
11
12import asyncio
13asyncio.run(main())
--- orig.py	2026-10-17 16:32:06.012684491 -0400
+++ fixed.py	2026-10-17 16:32:06.299684824 -0400
@@ -1,6 +1,6 @@
 async def fetch(flag):
     if not flag:
-        return
+        return None
     return 42
 
 async def main():
result: None
result2: 42
r2-p004 · Star-imports and module-level side effects
1__all__ = ['helper']
2
3def helper():
4 if config.get('skip'):
5 return
6 return 'loaded'
7
8config = {'skip': True}
9print(helper())
10config['skip'] = False
11print(helper())
--- orig.py	2026-10-17 16:32:06.462685013 -0400
+++ fixed.py	2026-10-17 16:32:06.633685212 -0400
@@ -2,7 +2,7 @@
 
 def helper():
     if config.get('skip'):
-        return
+        return None
     return 'loaded'
 
 config = {'skip': True}
None
loaded
r2-p005 · Custom __bool__ and None vs falsy comparison
1class FalsyObj:
2 def __bool__(self):
3 return False
4 def __eq__(self, other):
5 return True if other is None else False
6
7def check(val):
8 if val:
9 return
10 return val is None
11
12obj = FalsyObj()
13print(check(obj))
14print(check(None))
--- orig.py	2026-10-17 16:32:06.735685331 -0400
+++ fixed.py	2026-10-17 16:32:06.894685515 -0400
@@ -6,7 +6,7 @@
 
 def check(val):
     if val:
-        return
+        return None
     return val is None
 
 obj = FalsyObj()
False
True
r2-p006 · Nested function used as context manager
1def make_ctx():
2 entered = []
3 def ctx():
4 entered.append(True)
5 if not entered[0]:
6 return
7 return 'ok'
8 return ctx
9
10ctx_func = make_ctx()
11with ctx_func():
12 pass
13print('entered:', len([*make_ctx()()])) # This line is wrong, fixing:
14inner = make_ctx()
15with inner():
16 pass
17print('entered count:', len([True for _ in [inner()]]))
--- orig.py	2026-10-17 16:32:06.999685637 -0400
+++ fixed.py	2026-10-17 16:32:07.366686065 -0400
@@ -3,7 +3,7 @@
     def ctx():
         entered.append(True)
         if not entered[0]:
-            return
+            return None
         return 'ok'
     return ctx
 
r2-p007 · Comments and docstrings on rewritten return
1def process(data):
2 """Process data, early exit if invalid."""
3 if not data: # skip empty
4 return # nothing to do
5 return data.upper()
6
7print(process(''))
8print(process('hello'))
--- orig.py	2026-10-17 16:32:07.582686317 -0400
+++ fixed.py	2026-10-17 16:32:07.739686500 -0400
@@ -1,7 +1,7 @@
 def process(data):
     """Process data, early exit if invalid."""
     if not data:  # skip empty
-        return  # nothing to do
+        return None  # nothing to do
     return data.upper()
 
 print(process(''))
None
HELLO
r2-p008 · File write with non-str data and truncation
1def write_log(msg, critical):
2 if not critical:
3 return
4 with open('log.txt', 'w') as f:
5 f.write(msg) # expects str
6
7write_log('error', False)
8write_log('critical', True)
9with open('log.txt') as f:
10 print(f.read())
critical