fixhunt

ruff F632

labelled unknown
Runs / 2026-10-17T20-54-08-790Z-5ytj-ruff-F632ruff 0.17.12026-10-17 20:54 UTC8 programs × 2 round(s)16 programs, 10 fired, 10 fixed, 6 findings, 1 rejectedmodel cost $0.11

Analysis Nemotron 3 Super

Flags 'is' or 'is not' comparisons between any expression and literal constants (including strings, numbers, None, booleans, and compound literals like lists/dicts/sets).

Fix: Replaces 'is' with '==' and 'is not' with '!=' when comparing against literal constants, preserving all other expression structure.

11 hazards
  • Rewriting 'x is 5' to 'x == 5' changes behavior if x defines __eq__ with side effects, as the comparison may now execute where it previously did not due to short-circuiting in identity checks.
  • If x is a numpy array, 'x is 5' is always False (no rewrite needed), but 'x == 5' returns an array of booleans, altering truth value in conditionals and potentially causing ValueError.
  • When x is None, 'x is not None' rewritten to 'x != None' may invoke __eq__ on x if x is an object with a custom __eq__, whereas identity check bypasses it entirely.
  • For class instances where __eq__ is overridden to return NotImplemented, 'x == 5' may trigger fallback to __eq__ on 5 (int), which returns NotImplemented and results in False, while 'x is 5' was already False—so behavior may seem same but relies on different mechanics.
  • In code like 'if (x is 0) and y:', rewriting to 'if (x == 0) and y:' preserves logic only if x == 0 has no side effects; if __eq__ mutates state, evaluation order side effects differ.
  • If x is a string literal like 'hello', 'x is "hello"' may be True due to interning, but 'x == "hello"' is also True—so no behavioral change, but the fix is still applied; however, relying on interning is fragile and the rewrite is semantically safer.
  • When x is a bytes object from external input, 'x is b"a"' is almost always False, but 'x == b"a"' compares values—correcting the intent, yet if the code depended on object identity for caching or pooling, the rewrite breaks that assumption.
  • If the literal is a mutable compound like [] and x is a local variable, 'x is []' is False unless x is that exact list, but 'x == []' may be True for empty lists—fix corrects mistaken identity use, but if x was intended to be checked for being the same mutable object (e.g., in a cache), the rewrite loses that distinction.
  • In rare cases where x is a property that returns a new list on each access, 'x is []' is always False, and 'x == []' may be True if the property returns an empty list—fix is correct, but if the code relied on the identity check to detect initialization state via object uniqueness, the rewrite obscures intent.
  • Comments attached to the 'is' or 'is not' operator (e.g., 'x is # inline comment 5') may be lost or misplaced during rewrite if the tool does not preserve whitespace and comment tokens around the operator.
  • If the comparison is part of a larger expression like 'x is 5 or y is 10', rewriting each 'is' independently is safe, but if the literal is a complex expression mistakenly treated as constant (e.g., due to tool error), it could rewrite non-literals.

Findings

programoraclelabeljudgestatussummary
r1-p004fix-changes-behaviorlabelled safereal · highrelatedThe 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,…
r2-p001fix-changes-behaviorlabelled safeintended-change · highduplicateThe rule rewrites `x is []` to `x == []`, changing an identity comparison to a value comparison. The documentation explicitly states that `is` with literals is…
r2-p006fix-changes-behaviorlabelled safeintended-change · highduplicateThe rule rewrites 'x is []' to 'x == []' because identity comparison with a list literal is always False; the program demonstrates the semantic change by print…
r1-p006fix-changes-behaviorlabelled safeintended-change · lowduplicateThe rule rewrites 'is' to '==' for literal comparisons because identity checks against literals are unreliable and will become SyntaxWarnings in Python 3.8. Th…
r1-p007fix-changes-behaviorlabelled safeintended-change · lowrelatedThe rule rewrites identity comparison (`is`) to equality comparison (`==`) for literals, which is the documented semantic change. The program's custom class `M…
r2-p005fix-changes-behaviorlabelled safeintended-change · lowrelatedThe rule F632 explicitly rewrites identity comparisons (`is`) against literals to value comparisons (`==`) because `is` checks object identity, which is unreli…

Programs

idresulttitlehazard
r1-p001silent prediction offShadowed Builtinsshadowed builtins and names redefined later in the module
r1-p002silent prediction offInt/Float Coercionint/float/bool coercion, Decimal, division and rounding
r1-p003silent prediction offCustom __eq__ on None CheckWhen x is None, 'x is not None' rewritten to 'x != None' may invoke __eq__ on x if x is an object with a custom __eq__
r1-p004fix-changes-behaviorDict Ordering and Overridesdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r1-p005invalidException in Rewritten Codeexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p006fix-changes-behaviorProperty Returns New ListIf x is a property that returns a new list on each access, 'x is []' is always False, and 'x == []' may be True
r1-p007fix-changes-behaviorNumpy Array ComparisonIf x is a numpy array, 'x is 5' is always False, but 'x == 5' returns an array of booleans
r1-p008silent prediction offFile Modes and Truncationfile modes and truncation, partial writes, non-str data passed to write
r2-p001fix-changes-behaviorStar Imports and Module Effectsstar-imports, module-level side effects, __all__
r2-p002fix okGenerator Consumption Oncegenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p003fix okAsync Generator Await Orderingasync def/await ordering, async generators
r2-p004fix okShadowed Built-in Redefinitionshadowed builtins and names redefined later in the module
r2-p005fix-changes-behaviorString Interning SafetyIf x is a string literal like 'hello', 'x is "hello"' may be True due to interning, but 'x == "hello"' is also True—so no behavioral change, but the fix is still applied; however, relying on interning
r2-p006fix-changes-behaviorMutable List Identity vs EqualityIf the literal is a mutable compound like [] and x is a local variable, 'x is []' is False unless x is that exact list, but 'x == []' may be True for empty lists—fix corrects mistaken identity use, bu
r2-p007fix ok prediction offNumpy Array Comparison RiskIf x is a numpy array, 'x is 5' is always False (no rewrite needed), but 'x == 5' returns an array of booleans, altering truth value in conditionals and potentially causing ValueError.
r2-p008silent prediction offFile Write Truncation Hazardfile modes and truncation, partial writes, non-str data passed to write

Sources

r1-p001 · Shadowed Builtins
1def f():
2 none = 5
3 return none is None
4print(f()) # Prints False before fix; after fix, becomes `none == None` → raises NameError because `None` is overwritten as local var
False
r1-p002 · Int/Float Coercion
1from decimal import Decimal
2x = Decimal('5')
3y = 5
4print(x is y) # Always False
5z = x == y
6print(z) # True due to numeric equality; fix changes identity to equality, altering semantics
False
True
r1-p003 · Custom __eq__ on None Check
1class Obj:
2 def __eq__(self, other):
3 print('__eq__ called')
4 return True
5x = Obj()
6if x is not None:
7 print('identity')
8else:
9 print('not identity')
10# After fix: `x != None` calls __eq__ → prints '__eq__ called', then evaluates to False → prints 'not identity'
identity
r1-p004 · Dict Ordering and Overrides
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__)
False
True
r1-p005 · Exception in Rewritten Code (generated program does not parse: Traceback (most recent call last):)
1def get_val():
2 raise ValueError('bad')
3try:
4 x = get_val()
5 if x is not None: # Trigger: compares result of get_val() to None → but get_val() raises!
6except ValueError as e:
7 print('Caught:', e)
8# Note: The rule does NOT fire on `func() is 5` per spec → so expect_trigger=false? Wait — correction:
9# The rule says: 'stays silent on: func() is 5' → so this should NOT trigger.
10# Let's fix: make it a literal on right, variable on left.
11# Rewriting...
r1-p006 · Property Returns New List
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
False
True
r1-p007 · Numpy Array Comparison
1import sys
2sys.path.insert(0, '.')
3# Cannot import numpy per constraints → simulate with a mock class that mimics np.array behavior
4class MockArray:
5 def __init__(self, val):
6 self.val = val
7 def __eq__(self, other):
8 return [self.val == other] # returns list of bool → like np.array([5]) == 5 → [True]
9 def __is__(self, other):
10 return False # not real, but we simulate
11x = MockArray(5)
12print(x is 5) # False
13print(x == 5) # [True] → truth value of list is True → changes conditional behavior
14# Before: `if x is 5:` → False
15# After fix: `if x == 5:` → if [True]: → True → different branch
--- orig.py	2026-10-17 16:54:27.078806775 -0400
+++ fixed.py	2026-10-17 16:54:27.274809834 -0400
@@ -9,7 +9,7 @@
     def __is__(self, other):
         return False  # not real, but we simulate
 x = MockArray(5)
-print(x is 5)   # False
+print(x == 5)   # False
 print(x == 5)   # [True] → truth value of list is True → changes conditional behavior
 # Before: `if x is 5:` → False
 # After fix: `if x == 5:` → if [True]: → True → different branch
False
[True]
r1-p008 · File Modes and Truncation
1with open('test.txt', 'w') as f:
2 f.write(b'hello') # Writing bytes to text-mode file → raises TypeError
3 if f is not None: # Trigger: compares file object to None
4 print('file open')
5 else:
6 print('file closed')
7# After fix: `f != None` → still safe? But wait — the write fails earlier.
8# Let's move the check before write to isolate.
9# Rewriting...
r2-p001 · Star Imports and Module Effects
1__all__ = ['x']
2x = []
3if x is []:
4 print('same object')
5else:
6 print('different')
7del x
8from __main__ import *
9print(len(x))
--- orig.py	2026-10-17 16:54:35.720941205 -0400
+++ fixed.py	2026-10-17 16:54:36.128947530 -0400
@@ -1,6 +1,6 @@
 __all__ = ['x']
 x = []
-if x is []:
+if x == []:
   print('same object')
 else:
   print('different')
different
r2-p002 · Generator Consumption Once
1def gen():
2 yield 1
3 yield 2
4g = gen()
5if g is []:
6 print('unreachable')
7else:
8 print('not list')
9first = list(g)
10second = list(g)
11print(len(first), len(second))
--- orig.py	2026-10-17 16:54:36.335950738 -0400
+++ fixed.py	2026-10-17 16:54:36.496953232 -0400
@@ -2,7 +2,7 @@
   yield 1
   yield 2
 g = gen()
-if g is []:
+if g == []:
   print('unreachable')
 else:
   print('not list')
not list
2 0
r2-p003 · Async Generator Await Ordering
1async def agen():
2 yield 1
3async def main():
4 ag = agen()
5 if ag is []:
6 print('wrong')
7 else:
8 print('not list')
9 try:
10 await ag.__anext__()
11 except StopAsyncIteration:
12 print('done')
13import asyncio
14asyncio.run(main())
--- orig.py	2026-10-17 16:54:36.600954844 -0400
+++ fixed.py	2026-10-17 16:54:36.891959353 -0400
@@ -2,7 +2,7 @@
   yield 1
 async def main():
   ag = agen()
-  if ag is []:
+  if ag == []:
     print('wrong')
   else:
     print('not list')
not list
r2-p004 · Shadowed Built-in Redefinition
1list = 5
2if list is []:
3 print('matched')
4else:
5 print('no match')
6print(type(list))
--- orig.py	2026-10-17 16:54:37.055961893 -0400
+++ fixed.py	2026-10-17 16:54:37.220964448 -0400
@@ -1,5 +1,5 @@
 list = 5
-if list is []:
+if list == []:
   print('matched')
 else:
   print('no match')
no match
<class 'int'>
r2-p005 · String Interning Safety
1x = 'hello'
2if x is 'hello':
3 print('interned')
4else:
5 print('not interned')
6if x == 'hello':
7 print('equal')
8else:
9 print('not equal')
--- orig.py	2026-10-17 16:54:37.320965996 -0400
+++ fixed.py	2026-10-17 16:54:37.516969031 -0400
@@ -1,5 +1,5 @@
 x = 'hello'
-if x is 'hello':
+if x == 'hello':
   print('interned')
 else:
   print('not interned')
interned
equal
r2-p006 · Mutable List Identity vs Equality
1a = []
2b = []
3if a is []:
4 print('a is new list')
5else:
6 print('a is not new list')
7if a == []:
8 print('a is empty')
9else:
10 print('a is not empty')
11cache = []
12def get_cache():
13 return cache
14x = get_cache()
15if x is []:
16 print('cache hit')
17else:
18 print('cache miss')
--- orig.py	2026-10-17 16:54:37.612970517 -0400
+++ fixed.py	2026-10-17 16:54:37.771972979 -0400
@@ -1,6 +1,6 @@
 a = []
 b = []
-if a is []:
+if a == []:
   print('a is new list')
 else:
   print('a is not new list')
@@ -12,7 +12,7 @@
 def get_cache():
   return cache
 x = get_cache()
-if x is []:
+if x == []:
   print('cache hit')
 else:
   print('cache miss')
a is not new list
a is empty
cache miss
r2-p007 · Numpy Array Comparison Risk
1import numpy as np
2x = np.array([5])
3print('is check:', x is 5)
4try:
5 result = x == 5
6 print('== result:', result, type(result))
7 if result:
8 print('unexpected true')
9 else:
10 print('false or array')
11except Exception as e:
12 print('error:', type(e).__name__, str(e))
--- orig.py	2026-10-17 16:54:37.874974574 -0400
+++ fixed.py	2026-10-17 16:54:38.264980609 -0400
@@ -1,6 +1,6 @@
 import numpy as np
 x = np.array([5])
-print('is check:', x is 5)
+print('is check:', x == 5)
 try:
   result = x == 5
   print('== result:', result, type(result))
r2-p008 · File Write Truncation Hazard
1f = open('test.bin', 'wb')
2if f is None:
3 print('failed open')
4else:
5 print('opened ok')
6try:
7 f.write(b'hello')
8 f.write(123)
9except Exception as e:
10 print('write error:', type(e).__name__, str(e))
11finally:
12 f.close()
13import os
14print('file size:', os.path.getsize('test.bin'))
15os.remove('test.bin')
opened ok
write error: TypeError a bytes-like object is required, not 'int'
file size: 5