ruff RET502
labelled unknown
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
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | invalid | Lambda syntax error | Bare return inside lambda raises SyntaxError; fixing it makes invalid code valid. |
| r1-p002 | fix ok | Context manager confusion | Rewriting bare return to return None in __exit__ doesn't suppress exceptions but changes attribute visibility. |
| r1-p003 | fix ok | Finally clause interaction | Exception handling chain altered by explicit return vs implicit flow through finally. |
| r1-p004 | fix ok | Mutable default interference | Rewrite affects when mutation occurs relative to early exit; list persistence changed. |
| r1-p005 | invalid | Shadowed built-in clash | Local return override interacts with later global reassignment affecting closure capture. |
| r1-p006 | fix ok | Walrus and comprehension scope | Early return in nested scope alters walrus variable persistence in enclosing comprehension. |
| r1-p007 | fix ok | Parameter forwarding complexity | Explicit return None interferes with *args/**kwargs debugging introspection vs implicit. |
| r1-p008 | fix ok | Subclass override clarity | Explicit return None in base class method makes override intent clearer without behavior change. |
| r2-p001 | fix ok | Property decorator - return rewrite under @property | In functions decorated with @property, @staticmethod, or @classmethod, the rewrite behaves identically since these decorators do not alter return value semantics. |
| r2-p002 | fix ok | Runtime type annotations with typing imports | type annotations evaluated at runtime, typing-only imports |
| r2-p003 | fix ok | Async function - return None affects awaited value | In async functions, 'return' becomes 'return None', which may affect awaited values if callers expect no return value. |
| r2-p004 | fix ok | Star-imports and module-level side effects | star-imports, module-level side effects, __all__ |
| r2-p005 | fix ok | Custom __bool__ and None vs falsy comparison | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p006 | fix ok | Nested function used as context manager | 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 t |
| r2-p007 | fix ok | Comments and docstrings on rewritten return | comments and docstrings attached to the removed or moved code |
| r2-p008 | silent prediction off | File write with non-str data and truncation | file 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