fixhunt

ruff E712

labelled unknown
Runs / 2026-10-17T20-48-18-414Z-lp7t-ruff-E712ruff 0.17.12026-10-17 20:48 UTC8 programs × 2 round(s)16 programs, 16 fired, 0 fixed, 0 findings, 0 rejectedmodel cost $0.0051

Analysis Nemotron 3 Super

Flags equality comparisons (`==`, `!=`) to boolean literals `True` or `False`.

Fix: Replaces `expr == True` with `expr`, `expr != True` with `not expr`, `expr == False` with `not expr`, and `expr != False` with `expr`.

10 hazards
  • For `x == True`: if `x.__eq__` has side effects, replacing with `x` drops those effects.
  • For `x != True`: if `x.__ne__` has side effects, replacing with `not x` drops those effects and changes truth evaluation.
  • If `x` defines `__eq__` to return non-boolean (e.g., integer), `x == True` may be truthy/falsy differently than `x`.
  • If `x` defines `__eq__` returning a proxy object with custom `__bool__`, truth value may differ after rewrite.
  • When `x` is an instance with `__eq__` comparing to `True` via value (e.g., enum member), `is`-based logic would fail but `==` rewrite to `x` still uses `__eq__`—wait, no: the fix does NOT use `is`, it uses direct truth test, so this is actually a hazard: if user intended to check identity to `True` literal, the fix changes semantics.
  • If `x` is `None`, `None == False` is `False`, but `not None` is `True`—so `x == False` rewritten to `not x` flips meaning.
  • If `x` is `0`, `0 == False` is `True`, but `not 0` is `True`—same here; wait, `0 == False` is True, `not 0` is True: actually same. But `0 != False` is False, rewritten to `0` which is falsy → same? Wait: `x != False` → fix is `x`. For `x=0`: `0 != False` → `False` (since 0==False → True, so != is False). Fix: `x` → `0` → falsey → False. Same. But for `x=1`: `1 != False` → True (since 1==False → False). Fix: `x` → `1` → truthy → True. Still same? Actually yes for ints. Hazard only when `__eq__` overridden.
  • If the comparison appears in a larger expression without parentheses, operator precedence may change after rewrite (e.g., `x == True and y` → `x and y` vs original `(x == True) and y`).
  • If the comparison is part of a tuple or list literal, removing the comparison may alter whether the element is considered truthy in a surrounding `any()` or `all()` call.
  • If the comparison is inside a lambda or comprehension used later, the timing of evaluation may shift if the fix removes a function call that was previously guarded by the comparison structure.

Programs

idresulttitlehazard
r1-p001fired, no fixKeyword-only, positional-only, *args/**kwargs forwardingRewriting `x == True` changes argument passing behavior when used in a function call with mixed parameter kinds.
r1-p002fired, no fixWalrus, comprehensions scoping, late binding in closures and lambdasRewriting `expr == True` inside a comprehension alters variable capture due to changed expression structure affecting closure binding timing.
r1-p003fired, no fixPathlib vs os.path behavior on relative paths, symlinks, missing files, bytes pathsRewriting `path.exists() == True` changes behavior when the result is used in a context where boolean vs object identity matters for path resolution.
r1-p004fired, no fixMutable default arguments and aliasing after a rewriteRewriting `lst[0] == True` in a default argument expression changes when the list is mutated, due to altered evaluation timing.
r1-p005fired, no fixString formatting edge cases: %, format, f-strings, repr vs str, nested quotesRewriting `s == True` in an f-string changes behavior because the expression replaced affects how the value is interpolated.
r1-p006fired, no fixIf the comparison appears in a larger expression without parentheses, operator precedence may change after rewriteRewriting `x == True and y` to `x and y` changes precedence: original is (x == True) and y, rewritten is x and (y) but since 'and' is left-associative, same? Wait: x and y vs (x == True) and y. Actual
r1-p007fired, no fixType annotations evaluated at runtime, typing-only importsRewriting `func() == True` in a type annotation (e.g., as a default) causes the function to be called at annotation time, changing side effects.
r1-p008fired, no fixEvaluation order and side effects when expressions are merged, duplicated or deletedRewriting `a() == True and b()` to `a() and b()` changes evaluation order and side effects when a() has side effects and b() may not be called.
r2-p001fired, no fixGenerator consumption hazardGenerators vs lists, iterators consumed once, lazy vs eager evaluation - Rule rewrites generator expression comparison which consumes the iterator, changing later behavior.
r2-p002fired, no fixProperty side effect hazardClass attributes vs instance attributes, descriptors, properties with side effects - Property getter has side effects, rule drops them when replacing with direct truth test.
r2-p003fired, no fix__ne__ side effect hazardFor `x != True`: if `x.__ne__` has side effects, replacing with `not x` drops those effects and changes truth evaluation.
r2-p004fired, no fixNone vs False hazardIf `x` is `None`, `None == False` is `False`, but `not None` is `True`—so `x == False` rewritten to `not x` flips meaning.
r2-p005fired, no fixIdentity vs equality hazardWhen user intended to check identity to `True` literal, the fix changes semantics by using truth test instead of preserving `==` comparison.
r2-p006fired, no fixBytes vs str comparison hazardbytes vs str, encoding and newline handling, text vs binary modes - Comparison between bytes and string literals behaves differently after rewrite.
r2-p007fired, no fixString formatting interaction hazardstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes - Formatting in exception messages shows different values after rewrite.
r2-p008fired, no fixPathlib relative path hazardpathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths - Exists check interacts badly with rewrite when path doesn't exist.

Sources

r1-p001 · Keyword-only, positional-only, *args/**kwargs forwarding
1def f(pos, /, kw=None, *, kw_only=False):
2 return (pos, kw, kw_only)
3
4x = True
5result_before = f(1, x == True, kw_only=x == True)
6print('Before:', result_before)
7# After rewrite: x == True becomes x -> f(1, x, kw_only=x)
8# Since x is True, both calls should behave same here.
9# But if we make x something else...
10x = 1
11result_after = f(1, x == True, kw_only=x == True)
12print('After:', result_after)
Before: (1, True, True)
After: (1, True, True)
r1-p002 · Walrus, comprehensions scoping, late binding in closures and lambdas
1funcs = []
2for i in range(3):
3 if (val := i) == True: # Rule fires: val == True
4 funcs.append(lambda: val)
5 else:
6 funcs.append(lambda: -1)
7print([f() for f in funcs]) # Prints [0, -1, -1] because only i=0 makes val==True? Wait: i=0 -> val=0 == True? False. i=1 -> val=1 == True -> True. So index 1 captures val=1.
8# Correction: i=0: val=0 -> 0==True False -> lambda:-1
9# i=1: val=1 -> 1==True True -> lambda: val (which is 1 at definition time, but late binding)
10# i=2: val=2 -> False -> -1
11# All lambdas capture `val` from enclosing scope, which ends up as 2 after loop.
12# So prints [2, 2, 2] if all lambdas are called after loop.
13# But we want to see difference after rewrite.
14# Let's refactor to make it sensitive.
[-1, 2, -1]
r1-p003 · Pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths
1import os
2from pathlib import Path
3
4# Create a temporary file
5open('test.txt', 'w').close()
6try:
7 p = Path('test.txt')
8 # Rule fires on next line
9 exists_flag = p.exists() == True
10 print('p.exists() == True:', exists_flag)
11 # After rewrite: becomes p.exists()
12 # Same value, but if we use it in 'is' check...
13 print('p.exists() is True:', p.exists() is True)
14 print('p.exists() == True is:', (p.exists() == True) is True)
15finally:
16 os.remove('test.txt')
p.exists() == True: True
p.exists() is True: True
p.exists() == True is: True
r1-p004 · Mutable default arguments and aliasing after a rewrite
1def f(lst=[]):
2 if lst and lst[0] == True: # Rule fires here
3 lst.clear()
4 return 'cleared'
5 lst.append(False)
6 return lst
7
8print(f()) # First call: lst=[] -> if false -> append False -> [False]
9print(f()) # Second: lst=[False] -> lst[0]==True? False -> append -> [False, False]
10print(f()) # Third: [False, False] -> still False -> [False, False, False]
11# Now set lst[0] = True manually? Can't from outside.
12# Let's make it so we pass in a list.
13print('---')
14def g(lst):
15 if lst and lst[0] == True: # Rule fires
16 return 'yes'
17 return 'no'
18
19lst = [True]
20print(g(lst)) # 'yes'
21lst[0] = False
22print(g(lst)) # 'no'
23# After rewrite: becomes `if lst and lst[0]:`
24# For lst=[True]: lst[0] is True -> truthy -> 'yes'
25# lst=[False]: lst[0] is False -> falsy -> 'no' -> same.
26# Need where truthiness differs from == True.
27# Use [1]: 1 == True -> True, and 1 is truthy.
28# Use [0]: 0 == True -> False, 0 falsy.
29# Use []: error.
30# Use [2]: 2 == True -> False, but 2 truthy.
31# AHA!
32lst2 = [2]
33print('Before rewrite equivalent:', g(lst2)) # lst[0]==True -> 2==True -> False -> 'no'
34# After rewrite: `if lst and lst[0]:` -> lst=[2] -> lst[0]=2 (truthy) -> 'yes'
35# So we simulate by having two versions.
36# We'll print what the rewritten version would do.
37print('Rewritten would return:', 'yes' if lst2 and lst2[0] else 'no')
[False]
[False, False]
[False, False, False]
---
yes
no
Before rewrite equivalent: no
Rewritten would return: yes
r1-p005 · String formatting edge cases: %, format, f-strings, repr vs str, nested quotes
1s = True
2print(f'Value: {s == True}') # Rule fires inside {} -> becomes {s}
3# Before: {True} -> 'True'
4# After: {True} -> 'True' -> same.
5# Not good. Need where s is not boolean.
6s = 1
7print(f'Value: {s == True}') # Before: {True} -> 'True'
8# After: {1} -> '1'
9print(f'Before rewrite would show: True')
10print(f'After rewrite shows: {s}')
11# Output will show the after version, so we see '1' instead of 'True'.
Value: True
Value: True
Before rewrite would show: True
After rewrite shows: 1
r1-p006 · If the comparison appears in a larger expression without parentheses, operator precedence may change after rewrite
1x = 2
2y = 3
3result = x == True and y # Rule fires
4print('Result:', result) # Prints False
5# After rewrite becomes: x and y -> 2 and 3 -> 3
6print('Rewritten would be:', x and y)
Result: False
Rewritten would be: 3
r1-p007 · Type annotations evaluated at runtime, typing-only imports
1call_count = 0
2def func():
3 global call_count
4 call_count += 1
5 return True
6
7# Type annotation: if we put == True here, rule would fire, but annotations are expressions.
8# However, ruff does not lint inside type annotations? Actually it might.
9# Let's use it in a default arg that is annotated.
10def f(x: int = func() == True): # Rule fires on default value
11 return x
12
13print('First call:', f()) # func() called once, returns True -> True==True -> True
14print('Call count after first:', call_count)
15print('Second call:', f()) # Default is pre-evaluated? No: in Python, default args are evaluated at definition time.
16# So func() was called once at definition.
17# Thus second call uses same default.
18print('Call count after second:', call_count)
19# After rewrite: default becomes func()
20# So func() called at definition -> returns True -> default=True
21# Same behavior in this case.
22# Need where func() returns non-True.
23# Redefine.
First call: True
Call count after first: 1
Second call: True
Call count after second: 1
r1-p008 · Evaluation order and side effects when expressions are merged, duplicated or deleted
1def a():
2 print('a called')
3 return False # == True -> False
4
5def b():
6 print('b called')
7 return True
8
9print('Evaluating: a() == True and b()')
10result = a() == True and b() # Rule fires on a() == True
11print('Result:', result)
12# After rewrite: becomes a() and b()
13# a() returns False -> False and b() -> b() not called due to short-circuit.
14# Original: a() == True -> False -> False and b() -> b() not called.
15# Same.
16# Make a() return True.
17def a2():
18 print('a2 called')
19 return True
20
21print('\nWith a2 returning True:')
22result2 = a2() == True and b() # a2() -> True -> True==True -> True -> then b() called
23print('Result:', result2)
24# After rewrite: a2() and b() -> a2() -> True -> then b() called -> same.
25# Need where a() returns something truthy but not True.
26def a3():
27 print('a3 called')
28 return 1
29
30print('\nWith a3 returning 1:')
31result3 = a3() == True and b() # 1==True -> False -> so b() not called
32print('Result:', result3)
33# After rewrite: a3() and b() -> a3() returns 1 (truthy) -> so b() called
34print('Rewritten would call b() and return:', a3() and b())
Evaluating: a() == True and b()
a called
Result: False

With a2 returning True:
a2 called
b called
Result: True

With a3 returning 1:
a3 called
b called
Result: True
a3 called
b called
Rewritten would call b() and return: True
r2-p001 · Generator consumption hazard
1def gen():
2 yield 1
3 yield True
4
5g = gen()
6print('First comparison:', [i for i in g] == True)
7# After fix: [i for i in g] becomes truth value of list, consuming g
8try:
9 print('Second use:', list(g))
10except Exception as e:
11 print('Error on second use:', type(e).__name__, str(e))
First comparison: False
Second use: []
r2-p002 · Property side effect hazard
1class X:
2 def __init__(self):
3 self.calls = 0
4 @property
5 def value(self):
6 self.calls += 1
7 return self.calls == 1
8
9x = X()
10print('First:', x.value == True)
11print('Calls after first:', x.calls)
12print('Second:', x.value == True)
13print('Calls after second:', x.calls)
First: True
Calls after first: 1
Second: False
Calls after second: 2
r2-p003 · __ne__ side effect hazard
1class Y:
2 def __init__(self):
3 self.side = 0
4 def __ne__(self, other):
5 self.side += 1
6 return not (self == other)
7 def __eq__(self, other):
8 return other is True
9
10y = Y()
11print('Before !=:', y.side)
12result = y != True
13print('After !=:', y.side, 'result:', result)
14print('Not y:', not y)
Before !=: 0
After !=: 1 result: False
Not y: False
r2-p004 · None vs False hazard
1x = None
2print('x == False:', x == False)
3print('not x:', not x)
4# Show the difference in conditional context
5if x == False:
6 print('Takes == False branch')
7else:
8 print('Skips == False branch')
9if not x:
10 print('Takes not x branch')
11else:
12 print('Skips not x branch')
x == False: False
not x: True
Skips == False branch
Takes not x branch
r2-p005 · Identity vs equality hazard
1x = True
2print('x == True:', x == True)
3print('x is True:', x is True)
4print('bool(x):', bool(x))
5# After fix, x == True becomes x, which is truthy
6if x == True:
7 print('Equal branch taken')
8else:
9 print('Equal branch skipped')
10if x:
11 print('Truth branch taken')
12else:
13 print('Truth branch skipped')
x == True: True
x is True: True
bool(x): True
Equal branch taken
Truth branch taken
r2-p006 · Bytes vs str comparison hazard
1b = b'true'
2s = 'true'
3print('bytes == True:', b == True)
4print('str == True:', s == True)
5print('bool(bytes):', bool(b))
6print('bool(str):', bool(s))
7# Demonstrate that non-empty bytes/str are truthy despite != True
8if b == True:
9 print('Bytes equal True')
10else:
11 print('Bytes not equal True (but truthy)')
12if s == True:
13 print('Str equal True')
14else:
15 print('Str not equal True (but truthy)')
bytes == True: False
str == True: False
bool(bytes): True
bool(str): True
Bytes not equal True (but truthy)
Str not equal True (but truthy)
r2-p007 · String formatting interaction hazard
1class Z:
2 def __eq__(self, other):
3 return other == True and str(self) == 'magic'
4 def __str__(self):
5 return 'Z-object'
6 def __repr__(self):
7 return "Z()"
8
9z = Z()
10try:
11 result = z == True
12 print('Comparison result:', result)
13except Exception as e:
14 print('Error in ==:', type(e).__name__, str(e))
15# Show what the fix would produce
16print('Direct truth value:', bool(z))
17print('Formatted str:', f'{z}')
18print('Formatted repr:', f'{z!r}')
Comparison result: False
Direct truth value: True
Formatted str: Z-object
Formatted repr: Z()
r2-p008 · Pathlib relative path hazard
1import pathlib
2p = pathlib.Path('nonexistent.txt')
3print('Path exists:', p.exists())
4print('Exists == True:', p.exists() == True)
5print('not exists:', not p.exists())
6# Create then delete to show change
7p.touch()
8print('After touch - exists == True:', p.exists() == True)
9print('After touch - not exists:', not p.exists())
10p.unlink()
Path exists: False
Exists == True: False
not exists: True
After touch - exists == True: True
After touch - not exists: False