fixhunt

ruff E713

labelled unknown
Runs / 2026-10-17T20-48-53-941Z-ck6k-ruff-E713ruff 0.17.12026-10-17 20:48 UTC8 programs × 2 round(s)16 programs, 10 fired, 10 fixed, 0 findings, 5 rejectedmodel cost $0.0035

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

idresulttitlehazard
r1-p001fix okWalrus operator interactionwalrus, comprehensions scoping, late binding in closures and lambdas
r1-p002fix okLambda preservationIn a lambda, the rewrite preserves behavior as the element expression is unchanged.
r1-p003fix okMutable default and aliasingmutable default arguments and aliasing after a rewrite
r1-p004invalidShadowed builtinsshadowed builtins and names redefined later in the module
r1-p005fix okList comprehension iterationIn a list comprehension, the rewrite does not alter iteration or evaluation of the element expression.
r1-p006silentType annotation runtimetype annotations evaluated at runtime, typing-only imports
r1-p007fix ok prediction offTuple unpacking syntax errorIf the element is a tuple unpacking like 'not a, b in c', it is a SyntaxError and the rule does not fire.
r1-p008fix okClass vs instance attributesclass attributes vs instance attributes, descriptors, properties with side effects
r2-p001fix okList comprehension iteration unaffectedlist comprehension iteration (the rewrite does not alter iteration or evaluation)
r2-p002fix ok prediction offString formatting edge casesstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r2-p003fix okClass vs instance attributes, descriptors, propertiesclass attributes vs instance attributes, descriptors, properties with side effects
r2-p004fix okDict ordering, duplicate keys, __hash__/__eq__ overrides, setsdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r2-p005invalidShadowed builtins and names redefined latershadowed builtins and names redefined later in the module
r2-p006invalidElement expression has side effectsif the element expression has side effects (e.g., a function call), moving 'not' changes nothing since the element is still evaluated once.
r2-p007invalidCollection expression has side effectsif the collection expression has side effects, it is still evaluated once after the rewrite, preserving order.
r2-p008invalid'not' as a variable nameif 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')