ruff SIM220
labelled unknown
Analysis Nemotron 3 Super
The rule flags 'and' expressions where one operand is the logical negation of the other, such as 'x and not x', which always evaluates to False.
Fix: Replace the entire 'and' expression with the literal 'False'.
10 hazards
- Rewriting 'func() and not func()' to 'False' changes behavior if func() has side effects, as the second call is omitted but the first may still be intended to execute.
- In '(x + y) and not (x + y)', if x or y are properties with getters that have side effects, the rewrite eliminates the second evaluation but preserves the first, altering observable state.
- For 'obj.attr and not obj.attr', if obj.attr is a descriptor with a __get__ method that performs side effects, the rewrite reduces the number of invocations from two to one, changing behavior.
- When used as 'lst[0] and not lst[0]', if lst[0] triggers a __getitem__ that raises an exception on the second access due to state change from the first, the rewrite avoids the exception but may hide intended error handling.
- In 'd[key] and not d[key]', if the dictionary's __getitem__ has side effects (e.g., logging or caching), the rewrite halves the number of calls, affecting program diagnostics or performance.
- If the expression is part of a larger statement like 'return a and not a', replacing it with 'return False' is safe, but if it's in a context relying on the expression's type being something other than bool (though unlikely), it could cause issues—though Python treats it as bool, so low risk.
- Comments attached to the expression, such as 'a and not a # important check', would be lost if the autofix replaces the whole line or expression without preserving trailing comments.
- In a macro-generated or templated code context where the duplication is intentional for code generation symmetry, replacing with 'False' could break the generator's assumptions about paired evaluations.
- If the expression is inside a lambda or comprehension where the duplicate evaluation is meant to ensure idempotency or retry logic (though logically flawed), removing the duplicate alters the intended fault-tolerance pattern.
- When the expression appears in a debug or logging condition like 'if debug and not debug:', the rewrite to 'False' silences the condition, but if debug is a mutable flag changed by the first eval, the second eval was meant to catch the change—now missed.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Comment preservation | Trailing comment lost when replacing 'and' expression with False |
| r1-p002 | silent prediction off | Stateful __getitem__ exception hiding | Second list access hides exception from mutable state change |
| r1-p003 | silent prediction off | Type annotation runtime evaluation | Forward reference in annotation triggers NameError if skipped |
| r1-p004 | silent prediction off | Dictionary __getitem__ side effects | Side-effecting __getitem__ called half as often |
| r1-p005 | silent | Dict ordering and duplicates | Duplicate keys in dict literal; only last wins but both accessed |
| r1-p006 | silent prediction off | Star-imports and module side effects | Star import executes module top-level; skipping second call changes side effects |
| r1-p007 | silent prediction off | Function call side effects | Func with side effect called once instead of twice |
| r1-p008 | silent prediction off | Keyword-only and defaults | Default argument evaluated once; side effect skipped on second call |
| r2-p001 | fired, no fix | Keyword-only and defaults - default evaluated once | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p002 | fired, no fix | Larger statement context - return expression | If the expression is part of a larger statement like 'return a and not a', replacing it with 'return False' is safe, but if it's in a context relying on the expression's type being something other tha |
| r2-p003 | silent prediction off | Walrus and comprehension scoping | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p004 | silent prediction off | Class vs instance attributes with property side effect | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p005 | fired, no fix | Shadowed builtins - len redefined | shadowed builtins and names redefined later in the module |
| r2-p006 | silent prediction off | Debug flag mutability in condition | When the expression appears in a debug or logging condition like 'if debug and not debug:', the rewrite to 'False' silences the condition, but if debug is a mutable flag changed by the first eval, the |
| r2-p007 | silent prediction off | Dict ordering and __eq__ side effect | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r2-p008 | fired, no fix | Type annotation runtime evaluation | type annotations evaluated at runtime, typing-only imports |
Sources
r1-p001 · Comment preservation
1result = []
2for i in range(2):
3 val = i and not i # track iteration
4 result.append((val, i))
5print(result)
[(0, 0), (False, 1)]
r1-p002 · Stateful __getitem__ exception hiding
1class List:
2 def __init__(self):
3 self.data = [1]
4 self.accessed = 0
5 def __getitem__(self, idx):
6 self.accessed += 1
7 if self.accessed == 2:
8 raise ValueError('second access')
9 return self.data[idx]
10lst = List()
11try:
12 x = lst[0] and not lst[0]
13except ValueError as e:
14 print('caught:', type(e).__name__, str(e))
15else:
16 print('result:', x, 'accesses:', lst.accessed)
caught: ValueError second access
r1-p003 · Type annotation runtime evaluation
1def f():
2 return True
3if f() and not f(): # annotation-like use
4 pass
5try:
6 x
7except NameError as e:
8 print('NameError:', e)
9else:
10 print('x defined:', x)
NameError: name 'x' is not defined
r1-p004 · Dictionary __getitem__ side effects
1class Dict:
2 def __init__(self):
3 self.store = {'k': 1}
4 self.log = []
5 def __getitem__(self, key):
6 self.log.append(key)
7 return self.store[key]
8d = Dict()
9result = d['k'] and not d['k']
10print('result:', result, 'log:', d.log)
result: False log: ['k', 'k']
r1-p005 · Dict ordering and duplicates
1d = {'a': 1, 'a': 2}
2print(d['a'])
3# no and-not pattern here
2
r1-p006 · Star-imports and module side effects
1import sys
2mod = type('m', (), {})()
3mod.state = 0
4sys.modules['testmod'] = mod
5exec("""
6state = 0
7def func():
8 global state
9 state += 1
10 return state > 0
11""", mod.__dict__)
12from testmod import *
13if func() and not func():
14 pass
15print('testmod.state:', mod.state)
testmod.state: 2
r1-p007 · Function call side effects
1calls = []
2def f():
3 calls.append(len(calls))
4 return len(calls) < 2
5x = f() and not f()
6print('result:', x, 'calls:', calls)
result: True calls: [0, 1]
r1-p008 · Keyword-only and defaults
1def g(x, y=[]):
2 y.append(1)
3 return len(y)
4try:
5 a = g(0) and not g(0)
6except:
7 pass
8print('y length after:', len(g(0, [])))
r2-p001 · Keyword-only and defaults - default evaluated once
1def f(a, *, b=print('default') or 1):
2 return a and not a
3print(f(2))
4print(f(2))
default False False
r2-p002 · Larger statement context - return expression
1def g():
2 x = 1
3 return x and not x
4print(type(g()))
5print(g())
<class 'bool'> False
r2-p003 · Walrus and comprehension scoping
1y = 0
2lst = [(y := i) and not (y := i) for i in range(2)]
3print(lst)
4print(y)
[0, False] 1
r2-p004 · Class vs instance attributes with property side effect
1class C:
2 @property
3 def val(self):
4 print('prop')
5 return 1
6 def test(self):
7 return self.val and not self.val
8c = C()
9c.test()
prop prop
r2-p005 · Shadowed builtins - len redefined
1len = lambda x: print('len') or 42
2x = [1,2,3]
3print(x and not x)
4print(len and not len)
False False
r2-p006 · Debug flag mutability in condition
1debug = {'value': True}
2if debug['value'] and not debug.__setitem__('value', False) or True:
3 print('inside')
4else:
5 print('outside', debug['value'])
inside
r2-p007 · Dict ordering and __eq__ side effect
1class Eq:
2 def __eq__(self, other):
3 print('eq')
4 return True
5 def __bool__(self):
6 return True
7k = Eq()
8d = {k: 1}
9print(d[k] and not d[k])
r2-p008 · Type annotation runtime evaluation
1T = int
2if False:
3 x: T and not T # type: ignore
4print('ok')
ok