ruff SIM221
labelled unknown
Analysis Nemotron 3 Super
Flags 'or' expressions where one operand is the logical negation of the other, such as 'x or not x', which always evaluates to True.
Fix: Rewrites the expression to the literal True, preserving any surrounding expression structure but replacing the entire 'or' expression with True.
8 hazards
- The rewrite removes any side effects from evaluating the left operand first, such as in a function call with mutable state changes.
- If the left operand is a property accessor (__getattr__ or __getattribute__), its side effects are lost when replaced with True.
- When the operand is an indexing operation like lst[i], any __getitem__ side effects or exceptions are eliminated by the rewrite.
- In chained comparisons like (x < y) or not (x < y), the rewrite removes the evaluation of x and y entirely, altering behavior if they have side effects.
- If the expression appears in a context relying on its specific truthy value for type narrowing (e.g., in an if condition), replacing with True may affect static analysis but not runtime behavior—yet this is still a semantic shift in expression value.
- Comments attached to the operands or operator (e.g., x # comment or not # comment x) may be lost during the AST rewrite.
- In rare cases where the expression is used via eval() or exec() with dynamically scoped names, the rewrite could alter name resolution if operands contain undefined variables that would have raised NameError.
- If the operand is a short-circuiting expression itself (e.g., f() or g()), the rewrite prevents evaluation of the right-hand operand in the negation, removing potential side effects from g().
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Property Accessor Side Effects Lost | If the left operand is a property accessor (__getattr__ or __getattribute__), its side effects are lost when replaced with True. |
| r1-p002 | invalid | Shadowed Builtins and Redefined Names | shadowed builtins and names redefined later in the module |
| r1-p003 | silent prediction off | Chained Comparison Side Effects Removed | In chained comparisons like (x < y) or not (x < y), the rewrite removes the evaluation of x and y entirely, altering behavior if they have side effects. |
| r1-p004 | silent prediction off | Exceptions Raised Inside Rewritten Code | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r1-p005 | fired, no fix | Comments Lost During AST Rewrite | Comments attached to the operands or operator (e.g., x # comment or not # comment x) may be lost during the AST rewrite. |
| r1-p006 | silent prediction off | Side Effects from Left Operand Evaluation Removed | The rewrite removes any side effects from evaluating the left operand first, such as in a function call with mutable state changes. |
| r1-p007 | fired, no fix prediction off | Star-Imports and Module-Level Side Effects | star-imports, module-level side effects, __all__ |
| r1-p008 | invalid | Truthiness of Custom __bool__/__len__ | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p001 | silent prediction off | Left Operand Side Effects Removed | The rewrite removes any side effects from evaluating the left operand first, such as in a function call with mutable state changes. |
| r2-p002 | silent prediction off | Generator Consumption Hazard | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r2-p003 | fired, no fix | Type Narrowing Context | If the expression appears in a context relying on its specific truthy value for type narrowing (e.g., in an if condition), replacing with True may affect static analysis but not runtime behavior—yet t |
| r2-p004 | silent prediction off | Short-Circuiting Operand Hazard | If the operand is a short-circuiting expression itself (e.g., f() or g()), the rewrite prevents evaluation of the right-hand operand in the negation, removing potential side effects from g(). |
| r2-p005 | silent | Bytes vs Str Encoding Hazard | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p006 | silent prediction off | Property Accessor Side Effects Lost | If the left operand is a property accessor (__getattr__ or __getattribute__), its side effects are lost when replaced with True. |
| r2-p007 | silent prediction off | Mutable Default Arguments and Aliasing | mutable default arguments and aliasing after a rewrite |
| r2-p008 | silent prediction off | Evaluation Order and Side Effects Merged | evaluation order and side effects when expressions are merged, duplicated or deleted |
Sources
r1-p001 · Property Accessor Side Effects Lost
1class A:
2 def __init__(self):
3 self.count = 0
4 def __getattr__(self, name):
5 self.count += 1
6 return 42 if name == 'x' else None
7
8a = A()
9print(a.x or not a.x)
10print('count:', a.count)
42 count: 1
r1-p002 · Shadowed Builtins and Redefined Names (generated program does not parse: Traceback (most recent call last):)
1def or():
2 return False
3
4def not_():
5 return True
6
7print(or() or not not_())
8
9# Later redefinition
10def or():
11 return True
12
13def not_():
14 return False
15
16print(or() or not not_())
r1-p003 · Chained Comparison Side Effects Removed
1counter = 0
2
3def get_x():
4 global counter
5 counter += 1
6 return counter
7
8def get_y():
9 global counter
10 counter += 10
11 return counter
12
13print((get_x() < get_y()) or not (get_x() < get_y()))
14print('counter:', counter)
True counter: 11
r1-p004 · Exceptions Raised Inside Rewritten Code
1def f():
2 try:
3 raise ValueError('bad')
4 except ValueError as e:
5 print('caught:', e)
6 raise RuntimeError('worse') from e
7
8try:
9 print(f() or not f())
10except Exception as e:
11 print(type(e).__name__ + ':', e)
caught: bad RuntimeError: worse
r1-p005 · Comments Lost During AST Rewrite
1x = 0 # init
2print(
3 x # left operand
4 or
5 not # operator comment
6 x # right operand
7)
8print('x after:', x)
True x after: 0
r1-p006 · Side Effects from Left Operand Evaluation Removed
1state = []
2
3def log_and_return(val):
4 state.append(val)
5 return val
6
7print(log_and_return(1) or not log_and_return(1))
8print('state:', state)
1 state: [1]
r1-p007 · Star-Imports and Module-Level Side Effects
1__all__ = ['flag']
2flag = False
3
4# Simulate star-import by direct use
5print(flag or not flag)
6flag = True
7print(flag or not flag)
True True
r1-p008 · Truthiness of Custom __bool__/__len__ (generated program is not deterministic)
1class B:
2 def __init__(self, val):
3 self.val = val
4 def __bool__(self):
5 print('__bool__ called')
6 return self.val > 0
7
8b = B(-1)
9print(b or not b)
10b = B(1)
11print(b or not b)
r2-p001 · Left Operand Side Effects Removed
1counter = [0]
2def inc():
3 counter[0] += 1
4 return counter[0]
5x = inc()
6y = inc() or not inc()
7print('left:', x, 'middle:', y)
left: 1 middle: 2
r2-p002 · Generator Consumption Hazard
1def gen():
2 yield 1
3 yield 2
4g = gen()
5a = next(g)
6b = list(g) or not list(g)
7print('first:', a, 'rest:', b)
first: 1 rest: [2]
r2-p003 · Type Narrowing Context
1x = 0
2if x or not x:
3 print('branch taken')
4else:
5 print('branch not taken')
6print('x:', x)
branch taken x: 0
r2-p004 · Short-Circuiting Operand Hazard
1called = []
2def f():
3 called.append('f')
4 return 0
5def g():
6 called.append('g')
7 return 1
8result = (f() or g()) or not (f() or g())
9print('result:', result, 'called:', called)
result: 1 called: ['f', 'g']
r2-p005 · Bytes vs Str Encoding Hazard
1data = b'hello\nworld'
2text = data.decode()
3line_count = text.count('\n') or not text.count('\n')
4print('lines:', line_count)
lines: 1
r2-p006 · Property Accessor Side Effects Lost
1class Obj:
2 def __getattr__(self, name):
3 print(f'accessing {name}')
4 return 42
5obj = Obj()
6obj.counter = 0
7def get_val():
8 obj.counter += 1
9 return obj.value
10x = get_val() or not get_val()
11print('counter:', obj.counter, 'x:', x)
accessing value counter: 1 x: 42
r2-p007 · Mutable Default Arguments and Aliasing
1def f(x=[]):
2 x.append(1)
3 return len(x)
4a = f()
5b = f() or not f()
6print('a:', a, 'b:', b)
a: 1 b: 2
r2-p008 · Evaluation Order and Side Effects Merged
1log = []
2def side(x):
3 log.append(x)
4 return x
5result = side(1) or not side(2)
6print('log:', log, 'result:', result)
log: [1] result: 1