fixhunt

ruff SIM212

labelled unknown
Runs / 2026-10-17T20-36-18-559Z-s526-ruff-SIM212ruff 0.17.12026-10-17 20:36 UTC8 programs × 2 round(s)16 programs, 1 fired, 0 fixed, 0 findings, 0 rejectedmodel cost $0.0031

Analysis Nemotron 3 Super

Flags ternary conditional expressions where the condition is negated (`not x`) and suggests swapping the arms to use the positive condition.

Fix: Swaps the true and false arms of the ternary expression and removes the `not` operator from the condition, transforming `b if not a else a` into `a if a else b`.

10 hazards
  • If `a` has side effects (e.g., a function call), evaluating it twice in the fixed form changes behavior compared to evaluating it once in the original.
  • If `a` is an expression with mutable state, re-evaluating it may yield different results due to state changes between evaluations.
  • If `a` raises an exception during evaluation, the fixed form may raise it where the original did not (or vice versa), altering control flow.
  • If `a` is not idempotent (e.g., `pop()` on a list), duplicating its evaluation changes program state incorrectly.
  • If `a` is a volatile expression (e.g., reading hardware or time), re-evaluation may produce inconsistent values.
  • If `b` or `a` contain comments, the autofix may discard them during AST reconstruction.
  • If the expression appears in a lambda or comprehension, duplicating `a` could affect scoping or closure capture unexpectedly.
  • If `a` is used for its truthiness but is later mutated, the fixed form may observe a different value than intended due to re-evaluation.
  • If `a` is a property with a setter, accessing it twice may trigger unintended side effects.
  • If the surrounding code relies on the exact evaluation count of `a` for logging, debugging, or performance reasons, the fix alters observable behavior.

Programs

idresulttitlehazard
r1-p001fired, no fixEvaluation Count LoggingIf the surrounding code relies on the exact evaluation count of `a` for logging, debugging, or performance reasons, the fix alters observable behavior.
r1-p002silent prediction offException Raised in ConditionExceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p003silent prediction offAsync Generator Interactionasync def/await ordering, async generators
r1-p004silent prediction offCustom __bool__ and TruthinessTruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='
r1-p005silent prediction offComment Discard in AST ReconstructionIf `b` or `a` contain comments, the autofix may discard them during AST reconstruction.
r1-p006silent prediction offStar Import Side Effectstar-imports, module-level side effects, __all__
r1-p007silent prediction offMutable State Re-evaluationIf `a` is an expression with mutable state, re-evaluating it may yield different results due to state changes between evaluations.
r1-p008silent prediction offFile Write Truncation Hazardfile modes and truncation, partial writes, non-str data passed to write
r2-p001silent prediction offLambda captures changed by duplicate evaluationIf the expression appears in a lambda or comprehension, duplicating `a` could affect scoping or closure capture unexpectedly.
r2-p002silent prediction offShadowed built-in after redefinitionshadowed builtins and names redefined later in the module
r2-p003silent prediction offSide effect duplicated in fixIf `a` has side effects (e.g., a function call), evaluating it twice in the fixed form changes behavior compared to evaluating it once in the original.
r2-p004silent prediction offKeyword-only arg default re-evaluatedkeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r2-p005silent prediction offException raised in condition changes timingIf `a` raises an exception during evaluation, the fixed form may raise it where the original did not (or vice versa), altering control flow.
r2-p006silent prediction offProperty setter triggered twiceIf `a` is a property with a setter, accessing it twice may trigger unintended side effects.
r2-p007silent prediction offIterator consumed twicegenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p008silent prediction offBinary mode file write truncation hazardbytes vs str, encoding and newline handling, text vs binary modes

Sources

r1-p001 · Evaluation Count Logging
1count = 0
2class A:
3 def __bool__(self):
4 global count
5 count += 1
6 return False
7
8a = A()
9b = 'original'
10result = b if not a else a
11print(f'eval_count={count}, result={result}')
eval_count=1, result=original
r1-p002 · Exception Raised in Condition
1def bad():
2 raise ValueError('fail')
3
4try:
5 x = 'ok' if not bad() else 'no'
6except ValueError as e:
7 print(f'exception: {type(e).__name__}: {e}')
8else:
9 print(f'no exception, x={x}')
10finally:
11 print('finally ran')
exception: ValueError: fail
finally ran
r1-p003 · Async Generator Interaction
1async def gen():
2 yield 1
3 await asyncio.sleep(0)
4 yield 2
5
6async def main():
7 g = gen()
8 first = await g.__anext__()
9 # Rule fires on: second if not first else 0
10 second = first if not first else 0 # not first -> False, so second=first
11 print(f'first={first}, second={second}')
12 try:
13 await g.__anext__()
14 except StopAsyncIteration:
15 print('done')
16
17import asyncio
18asyncio.run(main())
first=1, second=0
r1-p004 · Custom __bool__ and Truthiness
1class X:
2 def __bool__(self):
3 return False # always false
4
5x = X()
6y = None
7# Rule fires: z if not x else y -> since not x is True, z is chosen
8z = 'selected'
9result = z if not x else y
10print(f'x is None: {x is y}, x == y: {x == y}, bool(x): {bool(x)}')
11print(f'result={result}')
x is None: False, x == y: False, bool(x): False
result=selected
r1-p005 · Comment Discard in AST Reconstruction
1# This comment should be preserved if possible
2val = 'keep' # inline comment
3flag = False
4# Rule fires on: val if not flag else 'drop'
5result = val if not flag else 'drop' # keep this comment
6print(f'result={result}')
result=keep
r1-p006 · Star Import Side Effect
1import sys
2sys.modules[__name__]._side = []
3
4def _init():
5 sys.modules[__name__]._side.append('loaded')
6
7_init()
8from __main__ import * # triggers side effect via _init
9
10# Rule fires: msg if not loaded else 'default'
11loaded = len(sys.modules[__name__]._side) > 0
12msg = 'hello'
13result = msg if not loaded else 'default'
14print(f'loaded={loaded}, result={result}, side={sys.modules[__name__]._side}')
loaded=True, result=default, side=['loaded']
r1-p007 · Mutable State Re-evaluation
1state = [True]
2
3def get_flag():
4 return state.pop() if state else False
5
6a = get_flag() # first call: True, state becomes []
7b = 'first'
8c = 'second'
9# Rule fires: b if not a else c
10# Original: not a -> not True -> False -> pick c -> 'second'
11# After fix: a if a else c -> True -> pick a -> 'first' (but a is already evaluated!)
12# Wait: we need to make sure a is re-evaluated in fix.
13# Let's restructure so a is a function call.
14print(f'a={a}')
15result = b if not get_flag() else c # get_flag() called again -> returns False
16print(f'result={result}')
a=True
result=first
r1-p008 · File Write Truncation Hazard
1with open('test.txt', 'w') as f:
2 f.write('initial')
3
4with open('test.txt', 'r+') as f:
5 f.seek(0)
6 data = f.read(1) # read 'i'
7 # Rule fires: f.write(data) if not data else f.write('X')
8 # But we want to see if fix changes behavior
9 # Instead: use ternary to decide what to write
10 to_write = data if not data else 'X' # data='i' -> truthy -> not data=False -> pick else: 'X'
11 f.write(to_write)
12 f.seek(0)
13 content = f.read()
14 print(f'content={repr(content)}')
15import os
16os.remove('test.txt')
content='initialX'
r2-p001 · Lambda captures changed by duplicate evaluation
1def outer():
2 x = []
3 vals = [lambda i=i: x.append(i) for i in range(2)]
4 f = vals[0] if not len(x) else vals[1]
5 return f()
6outer()
7print(len(x))
r2-p002 · Shadowed built-in after redefinition
1list = lambda: [99]
2x = list() if not False else list()
3print(x)
[99]
r2-p003 · Side effect duplicated in fix
1state = 0
2def get():
3 global state
4 state += 1
5 return state
6a = get() if not False else 0
7print(a, state)
1 1
r2-p004 · Keyword-only arg default re-evaluated
1def f(*, default=[]):
2 default.append(1)
3 return default
4x = f() if not False else f()
5print(len(x))
1
r2-p005 · Exception raised in condition changes timing
1def bad():
2 raise ValueError('fail')
3try:
4 x = 1 if not False else bad()
5except ValueError as e:
6 print('caught', e)
r2-p006 · Property setter triggered twice
1class C:
2 def __init__(self):
3 self._v = 0
4 self.count = 0
5 @property
6 def v(self):
7 return self._v
8 @v.setter
9 def v(self, val):
10 self._v = val
11 self.count += 1
12c = C()
13_.v = c.v if not False else 5
14print(c.count)
r2-p007 · Iterator consumed twice
1def gen():
2 yield 1
3 yield 2
4g = gen()
5first = next(g) if not False else next(g)
6print(first)
1
r2-p008 · Binary mode file write truncation hazard
1with open('test.bin', 'wb') as f:
2 pass
3data = b'hello' if not True else b'world'
4with open('test.bin', 'wb') as f:
5 f.write(data)
6with open('test.bin', 'rb') as f:
7 print(f.read())
8import os
9os.remove('test.bin')
b'world'