ruff E713
labelled unknown
Analysis Nemotron 3 Super
Flags uses of 'not' before a containment test like 'not x in y', suggesting to rewrite as 'x not in y'.
Fix: Rewrites 'not {element} in {collection}' to '{element} not in {collection}' by moving 'not' after the element and before 'in'.
10 hazards
- If the element expression has side effects (e.g., a function call), moving 'not' changes nothing since the element is still evaluated once.
- If the collection expression has side effects, it is still evaluated once after the rewrite, preserving order.
- In a lambda, the rewrite preserves behavior as the element expression is unchanged.
- In a list comprehension, the rewrite does not alter iteration or evaluation of the element expression.
- If 'not' is part of a larger expression like 'not x in y or z', the rewrite only applies to the 'not x in y' subexpression due to operator precedence, but the autofix may incorrectly rewrite only that part if not parenthesized.
- If the code contains a trailing comment on the same line, the autofix may not preserve it depending on implementation.
- If 'in' is overridden as an attribute (e.g., x.in), the syntax 'x in y' would already be invalid, so the rule does not fire.
- If the element is a tuple unpacking like 'not a, b in c', it is a SyntaxError and the rule does not fire.
- If the code uses 'not' as a variable name (e.g., not = 5; not x in y), it is invalid syntax and the rule does not fire.
- If the containment test is inside a string or comment, the rule does not fire as it is not executed code.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Walrus operator interaction | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p002 | fix ok | Lambda preservation | In a lambda, the rewrite preserves behavior as the element expression is unchanged. |
| r1-p003 | fix ok | Mutable default and aliasing | mutable default arguments and aliasing after a rewrite |
| r1-p004 | invalid | Shadowed builtins | shadowed builtins and names redefined later in the module |
| r1-p005 | fix ok | List comprehension iteration | In a list comprehension, the rewrite does not alter iteration or evaluation of the element expression. |
| r1-p006 | silent | Type annotation runtime | type annotations evaluated at runtime, typing-only imports |
| r1-p007 | fix ok prediction off | Tuple unpacking syntax error | If the element is a tuple unpacking like 'not a, b in c', it is a SyntaxError and the rule does not fire. |
| r1-p008 | fix ok | Class vs instance attributes | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p001 | fix ok | List comprehension iteration unaffected | list comprehension iteration (the rewrite does not alter iteration or evaluation) |
| r2-p002 | fix ok prediction off | String formatting edge cases | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p003 | fix ok | Class vs instance attributes, descriptors, properties | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p004 | fix ok | Dict ordering, duplicate keys, __hash__/__eq__ overrides, sets | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r2-p005 | invalid | Shadowed builtins and names redefined later | shadowed builtins and names redefined later in the module |
| r2-p006 | invalid | Element expression has side effects | if the element expression has side effects (e.g., a function call), moving 'not' changes nothing since the element is still evaluated once. |
| r2-p007 | invalid | Collection expression has side effects | if the collection expression has side effects, it is still evaluated once after the rewrite, preserving order. |
| r2-p008 | invalid | 'not' as a variable name | if the code uses 'not' as a variable name (e.g., not = 5; not x in y), it is invalid syntax and the rule does not fire. |
Sources
r1-p001 · Walrus operator interaction
1def outer():
2 xs = []
3 ys = []
4 # Rule triggers on: not (val := x) in xs
5 f = lambda: [(ys.append(val), val)[1] for x in range(3) if not (val := x) in xs]
6 xs.extend([0, 2])
7 result = f()
8 print('ys:', ys, 'result:', result)
9outer()
--- orig.py 2026-10-17 16:49:10.057103774 -0400 +++ fixed.py 2026-10-17 16:49:10.342109732 -0400 @@ -2,7 +2,7 @@ xs = [] ys = [] # Rule triggers on: not (val := x) in xs - f = lambda: [(ys.append(val), val)[1] for x in range(3) if not (val := x) in xs] + f = lambda: [(ys.append(val), val)[1] for x in range(3) if (val := x) not in xs] xs.extend([0, 2]) result = f() print('ys:', ys, 'result:', result)
ys: [1] result: [1]
r1-p002 · Lambda preservation
1lst = [1, 2, 3]
2# Rule triggers on: not x in lst
3f = lambda x: not x in lst
4print('lambda(0):', f(0))
5print('lambda(2):', f(2))
6print('list comp:', [f(i) for i in range(4)])
--- orig.py 2026-10-17 16:49:10.438111739 -0400 +++ fixed.py 2026-10-17 16:49:10.607115273 -0400 @@ -1,6 +1,6 @@ lst = [1, 2, 3] # Rule triggers on: not x in lst -f = lambda x: not x in lst +f = lambda x: x not in lst print('lambda(0):', f(0)) print('lambda(2):', f(2)) print('list comp:', [f(i) for i in range(4)])
lambda(0): True lambda(2): False list comp: [True, False, False, False]
r1-p003 · Mutable default and aliasing
1def f(x, container=[]):
2 # Rule triggers on: not x in container
3 if not x in container:
4 container.append(x)
5 return container.copy()
6a = f(1)
7b = f(2, a)
8c = f(3)
9print('a:', a, 'b:', b, 'c:', c)
--- orig.py 2026-10-17 16:49:10.712117468 -0400 +++ fixed.py 2026-10-17 16:49:10.875120876 -0400 @@ -1,6 +1,6 @@ def f(x, container=[]): # Rule triggers on: not x in container - if not x in container: + if x not in container: container.append(x) return container.copy() a = f(1)
a: [1, 2] b: [1, 2] c: [1, 3]
r1-p004 · Shadowed builtins (generated program does not parse: Traceback (most recent call last):)
1in = lambda x, y: x % y == 0 # shadow built-in 'in' via assignment (invalid, but we use name)
2# Actually: reuse 'in' as variable name to test resolution
3in_op = __import__('operator').contains
4lst = [2, 4, 6]
5# Rule triggers on: not x in lst -> but we can't write 'in' as variable and use it in expression
6# So instead: define a variable named 'in_' and use it via getattr? No.
7# Correct approach: use a variable that shadows the *name* used in the expression, but 'in' is keyword.
8# So we cannot shadow 'in'. Instead, test name resolution of left-hand side.
9# Let's use a variable named 'x' that is reassigned.
10x = 5
11lst = [1, 2, 3]
12# Rule triggers on: not x in lst
13print('not x in lst:', not x in lst)
14x = 4
15print('after x=4:', not x in lst)
16# Now redefine 'lst' as a property-like thing via closure? Not needed.
17# Hazard: if 'x' or 'lst' is redefined later, but we already did.
18# To match spec: redefine a name used in the element after the lambda.
19# Let's do:
20def make_check():
21 lst = [10, 20]
22 return lambda: not x in lst # x is free
23x = 99
24f = make_check()
25lst = [99, 88] # rebind lst after lambda defined
26try:
27 print('f():', f())
28except Exception as e:
29 print(type(e).__name__, str(e))
r1-p005 · List comprehension iteration
1counter = 0
2side_effects = []
3def gen():
4 global counter
5 counter += 1
6 side_effects.append(counter)
7 return counter * 10
8# Rule triggers on: not i in [10, 30, 50]
9result = [i for i in map(gen, range(5)) if not i in [10, 30, 50]]
10print('counter:', counter, 'side_effects:', side_effects, 'result:', result)
--- orig.py 2026-10-17 16:49:11.116125912 -0400 +++ fixed.py 2026-10-17 16:49:11.491133743 -0400 @@ -6,5 +6,5 @@ side_effects.append(counter) return counter * 10 # Rule triggers on: not i in [10, 30, 50] -result = [i for i in map(gen, range(5)) if not i in [10, 30, 50]] +result = [i for i in map(gen, range(5)) if i not in [10, 30, 50]] print('counter:', counter, 'side_effects:', side_effects, 'result:', result)
r1-p006 · Type annotation runtime
1from typing import List, Optional # typing-only import
2# Rule does NOT trigger on annotations
3x: int = 5
4y: List[int] = [1, 2, 3]
5# This is not a containment test with 'not', so silent
6if not hasattr(__import__('sys'), 'version'):
7 pass
8print('x:', x, 'y:', y)
x: 5 y: [1, 2, 3]
r1-p007 · Tuple unpacking syntax error
1try:
2 # This is a SyntaxError: cannot use 'not' before tuple unpacking in expression
3 # We cannot put it in code directly; must be in a string or fail to parse.
4 # Instead, demonstrate that such a line would be invalid.
5 compile('not a, b in [1, 2, 3]', '<string>', 'eval')
6except SyntaxError as e:
7 print('SyntaxError:', type(e).__name__, '-', str(e))
8# Valid contrast
9print('normal:', not 1 in [1, 2, 3])
--- orig.py 2026-10-17 16:49:11.852141283 -0400 +++ fixed.py 2026-10-17 16:49:12.019144770 -0400 @@ -6,4 +6,4 @@ except SyntaxError as e: print('SyntaxError:', type(e).__name__, '-', str(e)) # Valid contrast -print('normal:', not 1 in [1, 2, 3]) +print('normal:', 1 not in [1, 2, 3])
normal: False
r1-p008 · Class vs instance attributes
1class A:
2 shared = [1, 2]
3 def __init__(self, val):
4 self.instance_val = val
5 @property
6 def prop(self):
7 print('prop accessed')
8 return self.instance_val
9
10a = A(99)
11# Rule triggers on: not a.prop in a.shared
12print('Before:')
13try:
14 result = not a.prop in a.shared
15 print('not a.prop in a.shared:', result)
16except Exception as e:
17 print(type(e).__name__, str(e))
18# Change shared after eval
19a.shared.append(99)
20print('After append:')
21print('not a.prop in a.shared:', not a.prop in a.shared)
22# Trigger again via lambda to show rewrite would not change behavior
23f = lambda: not a.prop in a.shared
24print('lambda:', f())
--- orig.py 2026-10-17 16:49:12.123146940 -0400 +++ fixed.py 2026-10-17 16:49:12.286150340 -0400 @@ -11,14 +11,14 @@ # Rule triggers on: not a.prop in a.shared print('Before:') try: - result = not a.prop in a.shared + result = a.prop not in a.shared print('not a.prop in a.shared:', result) except Exception as e: print(type(e).__name__, str(e)) # Change shared after eval a.shared.append(99) print('After append:') -print('not a.prop in a.shared:', not a.prop in a.shared) +print('not a.prop in a.shared:', a.prop not in a.shared) # Trigger again via lambda to show rewrite would not change behavior -f = lambda: not a.prop in a.shared +f = lambda: a.prop not in a.shared print('lambda:', f())
Before: prop accessed not a.prop in a.shared: True After append: prop accessed not a.prop in a.shared: False prop accessed lambda: False
r2-p001 · List comprehension iteration unaffected
1results = []
2for i in range(3):
3 # Build inner list via comprehension
4 inner = [not j in [0, 1, 2] for j in range(i+1)]
5 results.append(sum(inner))
6print(results)
--- orig.py 2026-10-17 16:49:20.019311040 -0400 +++ fixed.py 2026-10-17 16:49:20.184314453 -0400 @@ -1,6 +1,6 @@ results = [] for i in range(3): # Build inner list via comprehension - inner = [not j in [0, 1, 2] for j in range(i+1)] + inner = [j not in [0, 1, 2] for j in range(i+1)] results.append(sum(inner)) print(results)
[0, 0, 0]
r2-p002 · String formatting edge cases
1x = 5
2y = [1, 2, 3]
3# Rule does not trigger: embedded in expressions
4print('%d' % (not x in y))
5print('{}'.format(not x in y))
6print(f'{not x in y}')
7print(repr(not x in y))
--- orig.py 2026-10-17 16:49:20.281316459 -0400 +++ fixed.py 2026-10-17 16:49:20.442319790 -0400 @@ -1,7 +1,7 @@ x = 5 y = [1, 2, 3] # Rule does not trigger: embedded in expressions -print('%d' % (not x in y)) -print('{}'.format(not x in y)) -print(f'{not x in y}') -print(repr(not x in y)) +print('%d' % (x not in y)) +print('{}'.format(x not in y)) +print(f'{x not in y}') +print(repr(x not in y))
1 True True True
r2-p003 · Class vs instance attributes, descriptors, properties
1class A:
2 attr = [1, 2]
3 @property
4 def prop(self):
5 print('prop called')
6 return [3]
7a = A()
8print(not 1 in a.attr) # triggers
9print(not 3 in a.prop) # triggers
10print(a.attr)
--- orig.py 2026-10-17 16:49:20.542321858 -0400 +++ fixed.py 2026-10-17 16:49:20.715325437 -0400 @@ -5,6 +5,6 @@ print('prop called') return [3] a = A() -print(not 1 in a.attr) # triggers -print(not 3 in a.prop) # triggers +print(1 not in a.attr) # triggers +print(3 not in a.prop) # triggers print(a.attr)
False prop called False [1, 2]
r2-p004 · Dict ordering, duplicate keys, __hash__/__eq__ overrides, sets
1class BadHash:
2 def __init__(self, v): self.v = v
3 def __hash__(self): return hash(self.v)
4 def __eq__(self, other): return self.v == other.v
5s = {BadHash(1), BadHash(1), BadHash(2)} # dedup by hash/eq
6print(not BadHash(1) in s)
7print(not BadHash(3) in s)
8d = {BadHash(1): 'a', BadHash(2): 'b'}
9print(not BadHash(1) in d)
10print(not BadHash(3) in d)
--- orig.py 2026-10-17 16:49:20.817327546 -0400 +++ fixed.py 2026-10-17 16:49:20.981330939 -0400 @@ -3,8 +3,8 @@ def __hash__(self): return hash(self.v) def __eq__(self, other): return self.v == other.v s = {BadHash(1), BadHash(1), BadHash(2)} # dedup by hash/eq -print(not BadHash(1) in s) -print(not BadHash(3) in s) +print(BadHash(1) not in s) +print(BadHash(3) not in s) d = {BadHash(1): 'a', BadHash(2): 'b'} -print(not BadHash(1) in d) -print(not BadHash(3) in d) +print(BadHash(1) not in d) +print(BadHash(3) not in d)
False True False True
r2-p005 · Shadowed builtins and names redefined later (generated program does not parse: Traceback (most recent call last):)
1in = lambda x, y: x not in y # shadow 'in'
2print(in(1, [1,2])) # calls our lambda
3print(not 1 in [1,2]) # triggers rule; uses built-in 'in'
4in = 42 # redefine later
5try:
6 print(in)
7except Exception as e:
8 print(type(e).__name__, e)
r2-p006 · Element expression has side effects (generated program does not parse: Traceback (most recent call last):)
1counter = 0
2def inc():
3 global counter
4 counter += 1
5 return counter
6print(not inc() in [1, 2])) # triggers; inc() called once
7print('counter:', counter)
8counter = 0
9print(inc() not in [1, 2])) # after fix equivalent
10print('counter:', counter)
r2-p007 · Collection expression has side effects (generated program does not parse: Traceback (most recent call last):)
1counter = 0
2def make_list():
3 global counter
4 counter += 1
5 return [counter]
6print(not 1 in make_list())) # triggers; make_list() called once
7print('counter:', counter)
8counter = 0
9print(1 not in make_list())) # after fix equivalent
10print('counter:', counter)
r2-p008 · 'not' as a variable name (generated program does not parse: Traceback (most recent call last):)
1try:
2 not = 5 # SyntaxError: invalid syntax
3except SyntaxError as e:
4 print(type(e).__name__, e)
5# Rule cannot fire on invalid syntax
6print('Rule does not trigger due to syntax error')