ruff F632
labelled unknown
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
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p004 | fix-changes-behavior | labelled safe | real · high | related | 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,… |
| r2-p001 | fix-changes-behavior | labelled safe | intended-change · high | duplicate | The rule rewrites `x is []` to `x == []`, changing an identity comparison to a value comparison. The documentation explicitly states that `is` with literals is… |
| r2-p006 | fix-changes-behavior | labelled safe | intended-change · high | duplicate | The 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-p006 | fix-changes-behavior | labelled safe | intended-change · low | duplicate | The rule rewrites 'is' to '==' for literal comparisons because identity checks against literals are unreliable and will become SyntaxWarnings in Python 3.8. Th… |
| r1-p007 | fix-changes-behavior | labelled safe | intended-change · low | related | The rule rewrites identity comparison (`is`) to equality comparison (`==`) for literals, which is the documented semantic change. The program's custom class `M… |
| r2-p005 | fix-changes-behavior | labelled safe | intended-change · low | related | The rule F632 explicitly rewrites identity comparisons (`is`) against literals to value comparisons (`==`) because `is` checks object identity, which is unreli… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Shadowed Builtins | shadowed builtins and names redefined later in the module |
| r1-p002 | silent prediction off | Int/Float Coercion | int/float/bool coercion, Decimal, division and rounding |
| r1-p003 | silent prediction off | Custom __eq__ on None Check | 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__ |
| r1-p004 | fix-changes-behavior | Dict Ordering and Overrides | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p005 | invalid | Exception in Rewritten Code | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p006 | fix-changes-behavior | Property Returns New List | If x is a property that returns a new list on each access, 'x is []' is always False, and 'x == []' may be True |
| r1-p007 | fix-changes-behavior | Numpy Array Comparison | If x is a numpy array, 'x is 5' is always False, but 'x == 5' returns an array of booleans |
| r1-p008 | silent prediction off | File Modes and Truncation | file modes and truncation, partial writes, non-str data passed to write |
| r2-p001 | fix-changes-behavior | Star Imports and Module Effects | star-imports, module-level side effects, __all__ |
| r2-p002 | fix ok | Generator Consumption Once | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p003 | fix ok | Async Generator Await Ordering | async def/await ordering, async generators |
| r2-p004 | fix ok | Shadowed Built-in Redefinition | shadowed builtins and names redefined later in the module |
| r2-p005 | fix-changes-behavior | String Interning Safety | 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 |
| r2-p006 | fix-changes-behavior | Mutable List Identity vs Equality | 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, bu |
| r2-p007 | fix ok prediction off | Numpy Array Comparison Risk | 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. |
| r2-p008 | silent prediction off | File Write Truncation Hazard | file 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