ruff FURB110: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
If the condition expression is a property with side effects in its getter, the rewrite invokes it once vs potentially twice.
Program
Output
sensor_reads: 2 alarm: 2
sensor_reads: 1 alarm: 1
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · high
The rule rewrites 's.reading if s.reading else 0' to 's.reading or 0', but the property getter has a side effect (incrementing a counter). The original evaluates the condition once, while the rewritten form would evaluate it twice if truthy, but Python's 'or' short-circuits so it evaluates once—however, the original ternary evaluates the condition twice when truthy (once for condition, once for value), so the fix changes the number of side-effect executions from 2 to 1. The documentation explicitly marks this fix as unsafe when side effects are present, yet the fix was applied anyway, causing a silent change in program behavior.
Explanation Nemotron 3 Super unverified
The property `reading` increments `_reads` each time it is accessed; evaluating `s.reading` twice in the original expression causes two increments, yielding alarm = 2 and final `_reads` = 2. The rewritten form `s.reading or 0` accesses the property only once (since its value is truthy), incrementing `_reads` once, so alarm = 1 and final `_reads` = 1. Thus the transformation changes the number of property evaluations, altering observable state.
Reproduce
- file
prog.py(the before program)- minimized program
- r2-p007.py
- evidence sheet
- r2-p007.md
- regression fixture
- r2-p007.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py ruff check --preview --isolated --select FURB110 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
FURB110 fix · FURB110 property access conditional expression · ruff ternary operator side effect optimization · FURB110 repeated attribute evaluation warning
- duplicate astral-sh/ruff/issues/11398 same rule FURB110 rewriting a ternary conditional with side-effecting property access into an 'or' expression, changing evaluation count
- related astral-sh/ruff/issues/8402 same rule FURB110 autofix category but different construct (pack of fuzzer errors vs property side-effect ternary)
Details
- program
- Property getter side effects
- hazard
- If the condition expression is a property with side effects in its getter, the rewrite invokes it once vs potentially twice.
- oracle
stdout before: sensor_reads: 2 alarm: 2 stdout after: sensor_reads: 1 alarm: 1- run
- 2026-10-17T21-28-24-508Z-1jyv-ruff-FURB110
generated program (16 lines) and the fix's diff
--- orig.py 2026-10-17 17:29:27.827044170 -0400 +++ fixed.py 2026-10-17 17:29:27.988041267 -0400 @@ -9,7 +9,7 @@ # First, access s.reading to get a truthy value (1) _first = s.reading # now _reads=1 # Condition: s.reading -> property access -alarm = s.reading if s.reading else 0 +alarm = s.reading or 0 print('sensor_reads:', s._reads, 'alarm:', alarm) # Original: two property gets -> _reads increases by 2 (from 1 to 3), alarm = second reading (3) # Fixed: one property get -> _reads increases by 1 (to 2), alarm = that value (2)
rule documentation given to the models
# if-exp-instead-of-or-operator (FURB110) Derived from the **refurb** linter. Fix is always available. ## What it does Checks for ternary `if` expressions that can be replaced with the `or` operator. ## Why is this bad? Ternary `if` expressions are more verbose than `or` expressions while providing the same functionality. ## Example ```python x, y = 1, 2 z = x if x else y ``` Use instead: ```python x, y = 1, 2 z = x or y ``` ## Fix safety This rule's fix is marked as unsafe in the event that the body of the `if` expression contains side effects or comments. For example, `foo` will be called twice in `foo() if foo() else bar()` (assuming `foo()` returns a truthy value), but only once in `foo() or bar()`.