fixhunt

ruff F632: fix changes program behavior

fix-changes-behaviorlabelled safeintended-change · lowduplicate
Findings / 2026-10-17T20-54-08-790Z-5ytj-ruff-F632 / r1-p006ruff 0.17.112 → 8 lines2026-10-17 20:54 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

If x is a property that returns a new list on each access, 'x is []' is always False, and 'x == []' may be True

Program

before
1class Container:
2 def __init__(self):
3 self._data = None
4 @property
5 def data(self):
6 return [] if self._data is None else self._data
7c = Container()
8print(c.data is []) # Always False (new list each time)
after
1class Container:
2 def __init__(self):
3 self._data = None
4 @property
5 def data(self):
6 return [] if self._data is None else self._data
7c = Container()
8print(c.data == []) # Always False (new list each time)

Output

stdout beforeexit 0
False
stdout afterexit 0
True
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory intended-change · low

The rule rewrites 'is' to '==' for literal comparisons because identity checks against literals are unreliable and will become SyntaxWarnings in Python 3.8. The program's comment acknowledges the original 'is []' comparison always returns False due to new list creation, while the fixed '== []' correctly returns True when the container's data property returns an empty list, demonstrating the intended semantic change from identity to value comparison.

Explanation Nemotron 3 Super unverified

The original expression `c.data is []` checks object identity; since `[]` creates a new empty list on each call to `.data`, it never matches the newly created literal in the condition, so the result is always False. After rewriting to `c.data == []`, equality compares values: both sides evaluate to empty lists with identical contents, yielding True despite being distinct objects. Thus the change from identity (`is`) to equality (`==`) alters the program’s observable output.

Reproduce

file
prog.py (the before program)
minimized program
r1-p006.py
evidence sheet
r1-p006.md
regression fixture
r1-p006.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 F632 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

F632 fix · F632 property returns new mutable object · is comparison against literal list in property accessor · identity check on freshly created container instance

  • related astral-sh/ruff/issues/12518 Same rule F632, but defect involves False == 0 comparison, not identity check against a fresh list literal.
  • related astral-sh/ruff/issues/11736 Same rule F632, but defect is a panic/crash on invalid syntax, not a behavior-changing fix.
  • related astral-sh/ruff/issues/9374 Same rule F632, but defect is an infinite loop between == and is on chained comparisons, not a fresh-list identity check.
  • duplicate astral-sh/ruff/issues/8589 Explicitly titled 'Catch variable is []' — the same F632 rewrite (is -> ==) on a fresh list literal from a property/accessor.
  • related astral-sh/ruff/issues/6891 Same rules F632/E712, but defect is an infinite loop on a generator expression, not a fresh-list identity check.
  • related astral-sh/ruff/issues/1057 Same rule F632, but defect is a crash extracting CST from a lambda default value from a lambda default, not a behavior-changing fix.

Details

program
Property Returns New List
hazard
If x is a property that returns a new list on each access, 'x is []' is always False, and 'x == []' may be True
oracle
stdout before: False stdout after: True
run
2026-10-17T20-54-08-790Z-5ytj-ruff-F632
generated program (12 lines) and the fix's diff
1class Container:
2 def __init__(self):
3 self._data = None
4 @property
5 def data(self):
6 return [] if self._data is None else self._data
7 def set_data(self, val):
8 self._data = val
9c = Container()
10print(c.data is []) # Always False (new list each time)
11print(c.data == []) # True → fix changes behavior
12# Before: identity → False; after: equality → True
--- orig.py	2026-10-17 16:54:26.812802623 -0400
+++ fixed.py	2026-10-17 16:54:26.974805153 -0400
@@ -7,6 +7,6 @@
     def set_data(self, val):
         self._data = val
 c = Container()
-print(c.data is [])   # Always False (new list each time)
+print(c.data == [])   # Always False (new list each time)
 print(c.data == [])   # True → fix changes behavior
 # Before: identity → False; after: equality → True
rule documentation given to the models
# is-literal (F632)

Derived from the **Pyflakes** linter.

Fix is always available.

## What it does
Checks for `is` and `is not` comparisons against literals, like integers,
strings, or lists.

## Why is this bad?
The `is` and `is not` comparators operate on identity, in that they check
whether two objects are the same object. If the objects are not the same
object, the comparison will always be `False`. Using `is` and `is not` with
constant literals often works "by accident", but are not guaranteed to produce
the expected result.

As of Python 3.8, using `is` and `is not` with constant literals will produce
a `SyntaxWarning`.

This rule will also flag `is` and `is not` comparisons against non-constant
literals, like lists, sets, and dictionaries. While such comparisons will
not raise a `SyntaxWarning`, they are still likely to be incorrect, as they
will compare the identities of the objects instead of their values, which
will always evaluate to `False`.

Instead, use `==` and `!=` to compare literals, which will compare the
values of the objects instead of their identities.

## Example
```python
x = 200
if x is 200:
    print("It's 200!")
```

Use instead:
```python
x = 200
if x == 200:
    print("It's 200!")
```

## References
- [Python documentation: Identity comparisons](https://docs.python.org/3/reference/expressions.html#is-not)
- [Python documentation: Value comparisons](https://docs.python.org/3/reference/expressions.html#value-comparisons)
- [_Why does Python log a SyntaxWarning for ‘is’ with literals?_ by Adam Johnson](https://adamj.eu/tech/2020/01/21/why-does-python-3-8-syntaxwarning-for-is-literal/)