fixhunt

ruff SIM222

labelled unknown
Runs / 2026-10-17T20-37-16-466Z-rek3-ruff-SIM222ruff 0.17.12026-10-17 20:37 UTC8 programs × 2 round(s)10 programs, 1 fired, 0 fixed, 0 findings, 3 rejectedmodel cost $0.0031

Analysis Nemotron 3 Super

Flags 'or' expressions containing at least one truthy literal where the entire expression can be simplified to True (in boolean context) or shortened by removing trailing falsy-or-truthy terms after the first truthy term.

Fix: Replace the entire 'or' expression with True when used in a boolean context (if/while/assert/etc.), otherwise replace it with the first truthy operand (short-circuiting the rest).

10 hazards
  • The rewrite removes side effects from evaluating operands after the first truthy term, changing behavior if those operands have side effects (e.g., function calls, assignments).
  • In non-boolean contexts, replacing 'x or [1] or y' with 'x or [1]' alters the result when x is falsy but [1] is truthy and y is used elsewhere via aliasing or mutation.
  • If the first truthy term is a mutable literal like [1], replacing the expression with just that literal may cause unintended sharing if the result is stored and mutated later.
  • Removing trailing operands can break code that relies on those operands for their type or value in subsequent expressions (e.g., unpacking, indexing).
  • The fix may incorrectly remove parentheses that were grouping the 'or' expression with other operators, altering precedence.
  • Comments attached to removed operands (e.g., 'x or [1] # important') would be lost.
  • In boolean contexts, replacing with True is safe only if the original expression was guaranteed truthy; however, if the context expects a falsy value (e.g., in 'not (x or [1])'), the logic flips incorrectly unless the whole negated expression is handled.
  • If the 'or' expression appears in a default argument or lambda, early termination changes when default values are evaluated.
  • Replacing part of a larger expression (e.g., 'z = (x or [1]) + 5') with a subexpression may break if the original relied on the full expression's value for arithmetic or concatenation.
  • When the first truthy term is not a literal but a variable (e.g., 'x or flag or y'), the fix should not apply per rule intent, but if misapplied, it would incorrectly shorten the expression.

Programs

idresulttitlehazard
r1-p001invalidKeyword-only, positional-only, *args/**kwargs forwardingRule SIM222 may rewrite `x or [1]` as `[1]` in a function call argument position, breaking intent when the original expression was meant to provide a fallback via short-circuiting, especially when def
r1-p002invalidMutable literal sharing after rewriteIf SIM222 replaces `x or [1]` with `[1]` and the result is stored and mutated, all references share the same list object, causing unintended cross-talk.
r1-p003silent prediction offMutable default arguments and aliasingRewriting `x or []` in a default argument to `[]` causes the mutable default to be shared across calls, whereas the original expression would create a new list each time `x` is falsy.
r1-p004silentFirst truthy term is a variable (near-miss)Rule SIM222 should not trigger on `x or flag or y` because the first term `x` is not a literal; however, if misapplied, it would incorrectly shorten to `x`, breaking fallback logic when `x` is falsy.
r1-p005silent prediction offClass vs instance attributes, descriptors, propertiesRewriting a class attribute access in an `or` expression (e.g., `self.x or [1]`) may lose descriptor invocation (like `__get__` or `@property`) if replaced with a literal, breaking lazy initialization
r1-p006silent prediction offComments attached to removed operandsWhen SIM222 removes trailing terms in an `or` expression (e.g., `x or [1] # important`), any inline comment on the removed operand is lost, potentially removing meaningful documentation.
r1-p007fired, no fixIncorrect parentheses removal altering precedenceThe fix may strip parentheses around an `or` expression that is part of a larger expression (e.g., in `not (x or [1])`), breaking grouping and changing operator precedence.
r1-p008silent prediction offWalrus, comprehensions, late binding in closuresIn a comprehension or lambda, rewriting `x or [1]` to `[1]` may break late-binding behavior where the variable `x` is expected to be re-evaluated per iteration, but the literal `[1]` is constant.
r2-p001silentVariable truthy term prevents shorteningWhen the first truthy term is not a literal but a variable (e.g., 'x or flag or y'), the fix should not apply per rule intent, but if misapplied, it would incorrectly shorten the expression.
r2-p002invalidpathlib vs os.path symlink resolutionpathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths

Sources

r1-p001 · Keyword-only, positional-only, *args/**kwargs forwarding (generated program does not parse: Traceback (most recent call last):)
1def f(/, a, *, b=None):
2 return a, b
3
4def g(x, /, *, y=None, **kw):
5 return f(x, b=(y or [1]), **kw)
6
7# Test: y=None triggers fallback to [1]
8try:
9 r = g(10)
10except Exception as e:
11 print('error:', type(e).__name__, str(e))
12else:
13 print('g(10):', r)
14
15# Now simulate rule rewrite: (y or [1]) -> [1] always
16# This breaks fallback when y is provided
17print('g(10, y=[]):', g(10, y=[])) # Should use [], but rewrite forces [1]
r1-p002 · Mutable literal sharing after rewrite (generated program is not deterministic)
1flag = 0
2lst1 = flag or [1]
3lst2 = flag or [1]
4print('initial ids:', id(lst1), id(lst2), lst1 is lst2)
5
6lst1.append(99)
7print('after lst1.append(99):', lst1, lst2)
8
9# Simulate incorrect rewrite: both become same [1] object
10# Correct behavior: each `flag or [1]` creates a new list
11# But if rule rewrites to `[1]` and it's reused, sharing occurs
r1-p003 · Mutable default arguments and aliasing
1def append_val(val, lst=None):
2 if lst is None:
3 lst = val or [] # Rule may fire here
4 lst.append(val)
5 return lst
6
7print('append_val(1):', append_val(1))
8print('append_val(2):', append_val(2))
9print('append_val(3):', append_val(3))
10
11# Expected: each call returns [1], [2], [3]
12# If rewritten to `lst = []`, then all calls share same list: [1, 2, 3]
r1-p004 · First truthy term is a variable (near-miss)
1x = 0
2flag = [1, 2]
3y = [3, 4]
4result = x or flag or y
5print('result:', result)
6
7# Simulate incorrect rewrite: x or flag or y -> x
8wrong_result = x
9print('incorrect rewrite:', wrong_result)
10
11# Show that x is falsy, so shortening loses the intended fallback
result: [1, 2]
incorrect rewrite: 0
r1-p005 · Class vs instance attributes, descriptors, properties
1class A:
2 _val = None
3 @property
4 def x(self):
5 if self._val is None:
6 self._val = [len(self.__dict__)] # side effect: capture size
7 return self._val
8
9a = A()
10print('first access:', a.x or [1]) # triggers property, returns [0]
11print('second access:', a.x or [1]) # returns [1] (cached)
12
13# If rewritten to `[1]`, property never called, _val remains None,
14# and all results are [1] regardless of state
first access: [0]
second access: [0]
r1-p006 · Comments attached to removed operands
1# This comment explains why [1] is the fallback
2flag = 0
3value = flag or [1] # important: non-empty list for processing
4print('value:', value)
5
6# Simulate rewrite: value = [1] # important -> but comment may be dropped
7# If comment is lost, we lose intent; if kept, it's misleading since no fallback occurs
value: [1]
r1-p007 · Incorrect parentheses removal altering precedence
1x = 0
2# Original: not (x or [1]) -> not ([1]) -> False
3# If parentheses stripped incorrectly: not x or [1] -> (not 0) or [1] -> True or [1] -> True
4result = not (x or [1])
5print('not (x or [1]):', result)
6
7# Show what happens if parentheses are removed
8result_bad = not x or [1]
9print('not x or [1] (precedence broken):', result_bad)
not (x or [1]): False
not x or [1] (precedence broken): True
r1-p008 · Walrus, comprehensions, late binding in closures
1flags = [0, 0, 0]
2# Walrus in list comprehension: each iteration should see updated flag via assignment
3results = [(flag := i) or [1] for i in flags]
4print('walrus results:', results)
5
6# Simulate rewrite: (flag := i) or [1] -> [1] always
7# Then results would be [[1], [1], [1]], losing the walrus assignment effect
8# Also show that flag ends up as last i only if walrus runs
9print('final flag:', flag)
10
11# Lambda late-binding: each lambda should capture current value of `or` expression
12funcs = [lambda: (x or [1]) for x in [0, 0, 0]]
13print('lambda results:', [f() for f in funcs])
walrus results: [[1], [1], [1]]
final flag: 0
lambda results: [[1], [1], [1]]
r2-p001 · Variable truthy term prevents shortening
1flag = 0
2x = []
3y = {}
4result = x or flag or y
5print('result:', result)
6# After rewrite (if misapplied): result = x or flag -> []
7# Correct: flag=0 (falsy), then y={} -> result = {}
result: {}
r2-p002 · pathlib vs os.path symlink resolution (generated program is not deterministic)
1import os
2import tempfile
3with tempfile.TemporaryDirectory() as d:
4 link = os.path.join(d, 'link')
5 target = os.path.join(d, 'target')
6 open(target, 'w').close()
7 os.symlink(target, link)
8 p = link
9 exists = os.path.exists(p) or (p := os.path.realpath(p)) or False
10 print('exists:', exists, 'resolved:', p if 'p' in locals() else None)
11 # Rule sees: os.path.exists(p) or [] or False -> but wait, adjust to trigger