# ruff FURB110: fix changes program behavior

Internal evidence sheet generated by fixhunt. Not an issue text. A human re-verifies, checks the duplicates
below, and writes the upstream report in their own words following the project's AI policy.

- Tool: ruff ruff 0.17.1
- Rule: FURB110 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): real, severity 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.
- Program: minimized from 16 to 10 lines

## Reproducer

```python
class Sensor:
    def __init__(self):
        self._reads = 0
    @property
    def reading(self):
        self._reads += 1
        return self._reads  # 1,2,3,...
s = Sensor()
alarm = s.reading if s.reading else 0
print('sensor_reads:', s._reads, 'alarm:', alarm)
```

## Commands

```sh
python3 prog.py
ruff check --preview --isolated --select FURB110 --fix prog.py
python3 prog.py
```

## Observed

```
stdout before:
sensor_reads: 2 alarm: 2
stdout after:
sensor_reads: 1 alarm: 1
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:42:53.110681546 -0400
+++ fixed.py	2026-10-22 13:42:55.122312032 -0400
@@ -6,5 +6,5 @@
         self._reads += 1
         return self._reads  # 1,2,3,...
 s = Sensor()
-alarm = s.reading if s.reading else 0
+alarm = s.reading or 0
 print('sensor_reads:', s._reads, 'alarm:', alarm)
```

## Why the rewrite is not equivalent (model draft, verify before use)

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.

## Upstream search

Queries: "FURB110 fix", "FURB110 property access conditional expression", "ruff ternary operator side effect optimization", "FURB110 repeated attribute evaluation warning"

- duplicate: https://github.com/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: https://github.com/astral-sh/ruff/issues/8402
  same rule FURB110 autofix category but different construct (pack of fuzzer errors vs property side-effect ternary)

<details><summary>All hits</summary>

- [github, closed] FURB110 may break code  — https://github.com/astral-sh/ruff/issues/11398
- [github, closed] Pack of fuzzer Autofix/Infinite loop errors [2] — https://github.com/astral-sh/ruff/issues/8402

</details>
