ruff RET504
labelled unknown
Analysis Nemotron 3 Super
Flags redundant variable assignments where the assigned variable is returned immediately afterward.
Fix: Remove the assignment statement and replace the returned variable with the expression originally assigned to it.
12 hazards
- Assignment inside a conditional block may skip execution when condition is false, but moving expression to return would evaluate it unconditionally.
- Assignment inside a loop body may execute zero or multiple times, but returning the expression directly evaluates it once before loop entry.
- Assignment inside a try block where finally reassigns the variable would lose the finally's effect on the returned value.
- Assignment to a variable that is also read in a lambda or nested function defined later in the same scope would break closure capture.
- Assignment to a variable that has the same name as an imported symbol would shadow the import only locally; removing the assignment could cause the return to use the imported value instead.
- Assignment with a walrus operator (:=) inside a list comprehension or other comprehension changes the iteration semantics if the variable is used elsewhere in the comprehension.
- Assignment to a variable that is annotated with a type comment or type hint would drop the annotation if the assignment is removed, affecting static analysis.
- Assignment that includes a delayed evaluation via a property setter would lose the setter invocation if the expression is returned directly.
- Assignment that occurs after a side-effecting expression (e.g., function call) but before a return would still have executed the side effect; removing the assignment does not drop the side effect, but if the expression is moved into the return, the side effect remains—however, if the expression is duplicated due to common subexpression assumptions, it could be evaluated twice.
- Assignment where the expression is a yield or yield-from expression would change the generator's behavior if the yield is moved into a return statement.
- Assignment where the variable is deleted later in the same block (e.g., 'del x') would cause a NameError if the return uses the variable after deletion, but removing the assignment avoids the deletion error—however, the fix assumes no such deletion exists between assignment and return, so this hazard is mitigated by the rule's pattern but still relevant if the linter misses intervening code.
- Assignment that is the only statement in a block that ends with a return, but the block is part of a multi-line statement (e.g., continuation lines), could break parsing if the fix removes indentation incorrectly.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Conditional block skip | Assignment inside a conditional block may skip execution when condition is false, but moving expression to return would evaluate it unconditionally. |
| r1-p002 | silent prediction off | Try/finally reassignment | Assignment inside a try block where finally reassigns the variable would lose the finally's effect on the returned value. |
| r1-p003 | fired, no fix | Generator iterator consumption | Generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r1-p004 | silent prediction off | Mutable default argument aliasing | Mutable default arguments and aliasing after a rewrite |
| r1-p005 | silent prediction off | Yield in expression changes generator behavior | Assignment where the expression is a yield or yield-from expression would change the generator's behavior if the yield is moved into a return statement. |
| r1-p006 | fired, no fix | Float division and rounding | int/float/bool coercion, Decimal, division and rounding |
| r1-p007 | silent prediction off | Type hint annotation dropped | Assignment to a variable that is annotated with a type comment or type hint would drop the annotation if the assignment is removed, affecting static analysis. |
| r1-p008 | invalid | Multi-line statement indentation break | Assignment that is the only statement in a block that ends with a return, but the block is part of a multi-line statement (e.g., continuation lines), could break parsing if the fix removes indentation |
| r2-p001 | fired, no fix | Type annotation evaluated at runtime with typing import | type annotations evaluated at runtime, typing-only imports |
| r2-p002 | silent prediction off | Property setter invocation lost by direct return | Assignment that includes a delayed evaluation via a property setter would lose the setter invocation if the expression is returned directly. |
| r2-p003 | silent prediction off | Keyword-only arg default eval once | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p004 | fired, no fix | Walrus in list comprehension alters iteration | Assignment with a walrus operator (:=) inside a list comprehension or other comprehension changes the iteration semantics if the variable is used elsewhere in the comprehension. |
| r2-p005 | silent prediction off | Lambda captures var before assignment overwrites it | Assignment to a variable that is also read in a lambda or nested function defined later in the same scope would break closure capture. |
| r2-p006 | silent prediction off | Yield moved to return breaks generator | Assignment where the expression is a yield or yield-from expression would change the generator's behavior if the yield is moved into a return statement. |
| r2-p007 | silent prediction off | Exception in expression affects finally | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p008 | fired, no fix | Shadowed built-in len redefined later | shadowed builtins and names redefined later in the module |
Sources
r1-p001 · Conditional block skip
1def f(b):
2 if b:
3 x = 1 / 0
4 else:
5 x = 42
6 return x
7print(f(True))
8print(f(False))
r1-p002 · Try/finally reassignment
1def f():
2 try:
3 x = 1
4 finally:
5 x = 2
6 return x
7print(f())
2
r1-p003 · Generator iterator consumption
1def f():
2 gen = (i for i in [1])
3 x = list(gen)
4 return x
5print(f())
6print(f())
[1] [1]
r1-p004 · Mutable default argument aliasing
1def f(a=[]):
2 x = a
3 x.append(1)
4 return x
5print(f())
6print(f())
[1] [1, 1]
r1-p005 · Yield in expression changes generator behavior
1def f():
2 x = (yield 1)
3 return x
4g = f()
5print(next(g))
6try:
7 g.send(2)
8except StopIteration as e:
9 print(e.value)
1 2
r1-p006 · Float division and rounding
1def f():
2 x = 1 / 3 * 3
3 return x
4print(repr(f()))
1.0
r1-p007 · Type hint annotation dropped
1def f():
2 x: int = 1
3 return x
4print(f())
5print(f.__annotations__)
1
{}r1-p008 · Multi-line statement indentation break (generated program does not parse: Traceback (most recent call last):)
1def f():\n if True:\n x = 1 \\
2 + 1\n return x\nprint(f())
r2-p001 · Type annotation evaluated at runtime with typing import
1from typing import List
2
3def f() -> List[int]:
4 x = []
5 return x
r2-p002 · Property setter invocation lost by direct return
1class C:
2 @property
3 def value(self):
4 return self._v
5 @value.setter
6 def value(self, v):
7 self._v = v * 2
8
9def f():
10 c = C()
11 c.value = 5
12 return c.value
r2-p003 · Keyword-only arg default eval once
1default = []
2def f(*, x=default):
3 x.append(1)
4 return x
5print(f())
6print(f())
[1] [1, 1]
r2-p004 · Walrus in list comprehension alters iteration
1def f():
2 data = [1, 2, 3]
3 result = [(x := i) * 2 for i in data if x > 0]
4 return result
5print(f())
r2-p005 · Lambda captures var before assignment overwrites it
1def f():
2 x = 10
3 g = lambda: x
4 x = 20
5 return g()
6print(f())
20
r2-p006 · Yield moved to return breaks generator
1def f():
2 x = (yield 1)
3 return x
4print(list(f()))
[1]
r2-p007 · Exception in expression affects finally
1def f():
2 try:
3 x = 1 / 0
4 except ZeroDivisionError as e:
5 print('caught:', e)
6 raise
7 return x
8try:
9 f()
10except ZeroDivisionError as e:
11 print('re-raised:', e)
caught: division by zero re-raised: division by zero
r2-p008 · Shadowed built-in len redefined later
1def f():
2 len = lambda x: 42
3 x = len([1, 2, 3])
4 return x
5print(f())
6print(len([1, 2, 3]))
42 3