ruff RET501
labelled unknown
Analysis Nemotron 3 Super
Flags explicit 'return None' statements where None is the only possible return value across all code paths in a function.
Fix: Rewrites 'return None' to 'return' when None is the only possible return value across all code paths in the function.
8 hazards
- The rewrite removes trailing comments attached to the return statement, altering source code readability.
- If the function contains a docstring after the return statement (unreachable but syntactically valid), the rewrite may affect parsing in edge cases involving AST transformations.
- In rare cases where 'return None' is used to deliberately suppress a generator yield (though syntactically invalid as mixed yield/return), the rewrite could interfere with tooling expectations.
- If the return statement is part of a larger expression via line continuation (e.g., using backslash), rewriting may break syntax due to changed token structure.
- The fix may interfere with custom decorators or metaclasses that inspect bytecode offsets, as removing 'None' alters the bytecode length and jump targets.
- In debugging contexts, explicit 'return None' may be used to set breakpoints; removing it could affect debug symbol mapping.
- If the function is overridden in a subclass and the return type is part of a contract (e.g., via structural typing), changing the return may affect type checker inferences.
- The rewrite may eliminate a deliberate 'return None' used to avoid returning a tuple in a single-expression function where implicit return would otherwise produce None anyway—no behavioral change, but may violate stylistic intent in codebases that enforce explicit returns for clarity.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Class attribute shadowing via descriptor | class attributes vs instance attributes, descriptors, properties with side effects |
| r1-p002 | fix ok | Float rounding and int coercion | int/float/bool coercion, Decimal, division and rounding |
| r1-p003 | fix ok | Mutable default aliasing after rewrite | mutable default arguments and aliasing after a rewrite |
| r1-p004 | silent prediction off | Explicit return None to avoid tuple in lambda-like context | The rewrite may eliminate a deliberate 'return None' used to avoid returning a tuple in a single-expression function where implicit return would otherwise produce None anyway—no behavioral change, but |
| r1-p005 | invalid | Docstring after return (unreachable) | If the function contains a docstring after the return statement (unreachable but syntactically valid), the rewrite may affect parsing in edge cases involving AST transformations. |
| r1-p006 | fix ok | File write with truncation and partial write | file modes and truncation, partial writes, non-str data passed to write |
| r1-p007 | silent prediction off | Trailing comment on return statement | The rewrite removes trailing comments attached to the return statement, altering source code readability. |
| r1-p008 | fix ok | Dict ordering with duplicate keys and custom hash | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r2-p001 | silent prediction off | Subclass override and structural typing | Changing `return None` to `return` may affect type checker inferences in overridden methods under structural typing (e.g., Protocol). |
| r2-p002 | fix ok prediction off | Mutable default argument aliasing | Rewriting `return None` to `return` does not change behavior, but the interaction with mutable defaults must be shown to be deterministic. |
| r2-p003 | fix ok | Keyword-only, positional-only, *args/**kwargs forwarding | The rewritten `return` statement must be shown to preserve argument forwarding behavior in complex signatures. |
| r2-p004 | invalid | Bytes vs str, encoding and newline handling | The rewrite must not alter byte/str handling in I/O contexts where `None` return is part of a pipeline. |
| r2-p005 | silent | Deliberate suppression of generator yield (tooling expectation) | Although syntactically invalid to mix yield and return, some tools may expect `return None` to signal intentional absence of yield; removing it may confuse static analysis. |
| r2-p006 | fix ok | Walrus, comprehensions scoping, late binding in closures | The rewritten `return` must not alter closure capture in nested scopes involving walrus or comprehensions. |
| r2-p007 | fix ok | String formatting edge cases: %, format, f-strings, repr vs str | The rewrite must not interfere with string formatting logic that depends on the function's return being None. |
| r2-p008 | silent prediction off | Avoiding tuple return in single-expression function | The rewrite removes explicit `return None` which was used for stylistic clarity to avoid implying a tuple return, though behaviorally identical. |
Sources
r1-p001 · Class attribute shadowing via descriptor
1class A:
2 x = 1
3 def __get__(self, obj, owner):
4 print('get', obj.x if obj else owner)
5 return obj.x if obj else 2
6
7def f():
8 a = A()
9 a.x = 99
10 return None # explicit
11
12r = f()
13print('returned:', r)
14print('a.x:', A().x)
--- orig.py 2026-10-17 16:31:36.470652535 -0400 +++ fixed.py 2026-10-17 16:31:36.639652704 -0400 @@ -7,7 +7,7 @@ def f(): a = A() a.x = 99 - return None # explicit + return # explicit r = f() print('returned:', r)
returned: None a.x: 1
r1-p002 · Float rounding and int coercion
1from decimal import Decimal, getcontext
2getcontext().prec = 3
3
4def f():
5 x = Decimal('1.005')
6 y = float(round(x, 2))
7 z = int(y * 100)
8 return None # explicit
9
10r = f()
11print('returned:', r)
12print('z:', z if 'z' in locals() else 'undefined')
--- orig.py 2026-10-17 16:31:36.738652804 -0400 +++ fixed.py 2026-10-17 16:31:36.917652983 -0400 @@ -5,7 +5,7 @@ x = Decimal('1.005') y = float(round(x, 2)) z = int(y * 100) - return None # explicit + return # explicit r = f() print('returned:', r)
returned: None z: undefined
r1-p003 · Mutable default aliasing after rewrite
1def f(l=[]):
2 l.append(1)
3 return None # explicit
4
5a = f()
6b = f()
7print('returned a:', a)
8print('returned b:', b)
9print('default list:', f.__defaults__[0])
--- orig.py 2026-10-17 16:31:37.032653099 -0400 +++ fixed.py 2026-10-17 16:31:37.204653272 -0400 @@ -1,6 +1,6 @@ def f(l=[]): l.append(1) - return None # explicit + return # explicit a = f() b = f()
returned a: None returned b: None default list: [1, 1]
r1-p004 · Explicit return None to avoid tuple in lambda-like context
1def f():
2 return None # explicit for clarity; avoids tuple perception
3
4g = lambda: None
5print('f() is g():', f() is g())
6print('type(f()):', type(f()))
7print('f().__class__:', f().__class__)
f() is g(): True type(f()): <class 'NoneType'> f().__class__: <class 'NoneType'>
r1-p005 · Docstring after return (unreachable) (generated program is not deterministic)
1def f():
2 return None
3 """This docstring is unreachable but legal."""
4 x = 1
5
6try:
7 r = f()
8 print('returned:', r)
9 print('locals after:', locals())
10except Exception as e:
11 print(type(e).__name__, str(e))
r1-p006 · File write with truncation and partial write
1def f():
2 with open('test.txt', 'w') as fp:
3 fp.write(b'hello') # TypeError expected
4 return None # explicit
5
6try:
7 f()
8except Exception as e:
9 print(type(e).__name__, str(e))
10finally:
11 try:
12 with open('test.txt') as fp:
13 print('file content:', repr(fp.read()))
14 except:
15 print('file content: (unreadable)')
--- orig.py 2026-10-17 16:31:37.529653600 -0400 +++ fixed.py 2026-10-17 16:31:37.706653778 -0400 @@ -1,7 +1,7 @@ def f(): with open('test.txt', 'w') as fp: fp.write(b'hello') # TypeError expected - return None # explicit + return # explicit try: f()
TypeError write() argument must be str, not bytes file content: ''
r1-p007 · Trailing comment on return statement
1def f():
2 return None # important: signals completion
3
4r = f()
5print('returned:', r)
6print('comment lost?')
returned: None comment lost?
r1-p008 · Dict ordering with duplicate keys and custom hash
1class K:
2 def __init__(self, v):
3 self.v = v
4 def __hash__(self):
5 return hash(self.v)
6 def __eq__(self, other):
7 return isinstance(other, K) and self.v == other.v
8
9def f():
10 d = {K(1): 'a', K(1): 'b'} # duplicate key
11 return None # explicit
12
13r = f()
14print('returned:', r)
15print('dict built?')
--- orig.py 2026-10-17 16:31:37.942654016 -0400 +++ fixed.py 2026-10-17 16:31:38.111654188 -0400 @@ -8,7 +8,7 @@ def f(): d = {K(1): 'a', K(1): 'b'} # duplicate key - return None # explicit + return # explicit r = f() print('returned:', r)
returned: None dict built?
r2-p001 · Subclass override and structural typing
1from typing import Protocol
2
3class P(Protocol):
4 def f(self) -> None: ...
5
6class Base:
7 def f(self):
8 return None
9
10class Derived(Base):
11 def f(self):
12 return None
13
14x: P = Derived()
15print(type(x.f()))
<class 'NoneType'>
r2-p002 · Mutable default argument aliasing
1def f(a=[]):
2 a.append(1)
3 return None
4
5print(f())
6print(f())
7print(f())
--- orig.py 2026-10-17 16:31:42.459658638 -0400 +++ fixed.py 2026-10-17 16:31:42.624658810 -0400 @@ -1,6 +1,6 @@ def f(a=[]): a.append(1) - return None + return print(f()) print(f())
None None None
r2-p003 · Keyword-only, positional-only, *args/**kwargs forwarding
1def f(pos, /, *, kw=None, **kwargs):
2 if pos == 0:
3 return
4 return None
5
6print(f(0))
7print(f(1, kw=2, extra=3))
--- orig.py 2026-10-17 16:31:42.726658915 -0400 +++ fixed.py 2026-10-17 16:31:42.895659090 -0400 @@ -1,7 +1,7 @@ def f(pos, /, *, kw=None, **kwargs): if pos == 0: return - return None + return print(f(0)) print(f(1, kw=2, extra=3))
None None
r2-p004 · Bytes vs str, encoding and newline handling (generated program does not parse: Traceback (most recent call last):)
1def f():
2 b = b'hello\nworld'
3 if b'
4' in b:
5 return None
6 return b
7
8result = f()
9print(result is None)
r2-p005 · Deliberate suppression of generator yield (tooling expectation)
1def f():
2 if False:
3 yield 1
4 return None
5
6try:
7 list(f())
8 print('no error')
9except Exception as e:
10 print(type(e).__name__, e)
no error
r2-p006 · Walrus, comprehensions scoping, late binding in closures
1def f():
2 x = 0
3 if (x := x + 1) > 0:
4 return
5 return None
6
7print(f())
--- orig.py 2026-10-17 16:31:43.279659489 -0400 +++ fixed.py 2026-10-17 16:31:43.440659657 -0400 @@ -2,6 +2,6 @@ x = 0 if (x := x + 1) > 0: return - return None + return print(f())
None
r2-p007 · String formatting edge cases: %, format, f-strings, repr vs str
1def f():
2 s = 'hello'
3 if len(s) == 5:
4 return
5 return None
6
7print('Result: {}'.format(f()))
--- orig.py 2026-10-17 16:31:43.540659761 -0400 +++ fixed.py 2026-10-17 16:31:43.699659927 -0400 @@ -2,6 +2,6 @@ s = 'hello' if len(s) == 5: return - return None + return print('Result: {}'.format(f()))
Result: None
r2-p008 · Avoiding tuple return in single-expression function
1def f():
2 return None
3
4print(type(f()))
<class 'NoneType'>