fixhunt

ruff F632: fix changes program behavior

fix-changes-behaviorlabelled safereal · highrelated
Findings / 2026-10-17T20-54-08-790Z-5ytj-ruff-F632 / r1-p004ruff 0.17.19 → 5 lines2026-10-17 20:54 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

dict ordering, duplicate keys, __hash__/__eq__ overrides, sets

Program

before
1class BadDict:
2 def __eq__(self, other):
3 return 42
4d = BadDict()
5print(d is {}) # False
after
1class BadDict:
2 def __eq__(self, other):
3 return 42
4d = BadDict()
5print(d == {}) # False

Output

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

Judge Nemotron 3 Ultra advisory real · high

The rule rewrites 'd is {}' to 'd == {}', changing an identity check into a value comparison. The program defines a class with a custom __eq__ that returns 42, so the equality comparison invokes that method and prints 42, whereas the original identity comparison correctly returned False. The documentation presents the rewrite as a safe equivalence for literal comparisons, but it is not equivalent when the left-hand operand overrides equality.

Explanation Nemotron 3 Super unverified

The original expression `d is {}` checks object identity between instance `d` and a newly created empty dict literal; since they are distinct objects, it evaluates to `False`. The rewrite replaces this with `d == {}`, which invokes `__eq__` on `d` (defined in `BadDict`) passing the new dict as `other`. That method returns the integer `42`, so the print outputs `42` instead of `False`, changing both type and value. Thus the transformation alters program semantics.

Reproduce

file
prog.py (the before program)
minimized program
r1-p004.py
evidence sheet
r1-p004.md
regression fixture
r1-p004.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 · ruff F632 identity comparison dict · F632 replace is with equals literal · rule F632 fix object equality check

  • related astral-sh/ruff/issues/12518 Same rule F632 but different construct: false positive when comparing `False == 0` instead of identity comparison with a custom `__eq__`.
  • related astral-sh/ruff/issues/11736 Same rule F632 but different construct: panic on invalid syntax rather than behavior-changing fix.
  • related astral-sh/ruff/issues/9374 Same rule F632 but different construct: infinite loop toggling `==`/`is` in a chained comparison.
  • related astral-sh/ruff/issues/8589 Same rule F632 but different construct: catching `variable is []` literal comparison.
  • related astral-sh/ruff/issues/6891 Same rule F632 but different construct: infinite loop with E712 on generator expression.
  • related astral-sh/ruff/issues/1057 Same rule F632 but different construct: choking on invalid comparison inside a lambda default argument.

Details

program
Dict Ordering and Overrides
hazard
dict ordering, duplicate keys, __hash__/__eq__ overrides, sets
oracle
stdout before: False stdout after: 42
run
2026-10-17T20-54-08-790Z-5ytj-ruff-F632
generated program (9 lines) and the fix's diff
1class BadDict:
2 def __eq__(self, other):
3 return len(other) == 0
4 def __hash__(self):
5 return 42
6d = BadDict()
7print(d is {}) # False
8print(d == {}) # True after fix → triggers __eq__
9# Before fix: identity check → False; after fix: equality → True (calls __eq__)
--- orig.py	2026-10-17 16:54:26.411796362 -0400
+++ fixed.py	2026-10-17 16:54:26.574798907 -0400
@@ -4,6 +4,6 @@
     def __hash__(self):
         return 42
 d = BadDict()
-print(d is {})     # False
+print(d == {})     # False
 print(d == {})     # True after fix → triggers __eq__
 # Before fix: identity check → False; after fix: equality → True (calls __eq__)
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/)