Skip FURB108 for complex expressions that depend on short-circuit evaluation#369
Skip FURB108 for complex expressions that depend on short-circuit evaluation#369worksbyfriday wants to merge 4 commits intodosisod:masterfrom
Conversation
Member access isn't always safe as class properties can run custom code: class C:
def __init__(self) -> None:
self.x = 0
@property
def p(self) -> int:
x = self.x
self.x += 1
return x
c = C()
for _ in range(2):
condition = c.p == 0 or c.p == 1
# condition = c.p in (0, 1)
print(c.x)The code currently prints This was an issue before this PR, but I'm saying that this will still be an issue after this PR. The same issue applies for "safe" binary/unary expressions, since you can override |
|
Good catch — you're right that member access, unary operators, and binary operators aren't safe since I've tightened The pre-existing issue you mentioned (common expression like |
1237de7 to
ab16485
Compare
FURB108 suggests replacing `x == a or x == b` with `x in (a, b)`, but this breaks short-circuit evaluation. When the right operand depends on the left comparison being false (e.g., guard patterns like `cutoff == 0 or events[cutoff - 1] == 0`), eagerly evaluating all operands in the `in` tuple can raise exceptions. Only emit FURB108 when the non-common operands (those placed in the tuple) are "simple" expressions: literals, names, member access, and basic arithmetic. Skip when they contain IndexExpr or CallExpr, which may have side effects or raise exceptions. Fixes dosisod#350
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add test cases for MemberExpr (c.y), UnaryExpr (-1), and OpExpr (a + b) as non-common operands that are simple and should still trigger FURB108 - Add pragma: no cover for unreachable fallback returns (matches existing pattern in common.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d0bc982 to
c2f4b25
Compare
Summary
Fixes #350.
FURB108 suggests replacing
x == a or x == bwithx in (a, b), but this transformation breaks short-circuit evaluation. When the right-side operand depends on the left comparison being false (guard patterns), eagerly evaluating all tuple operands can raise exceptions:Approach
Following the approach outlined in #350, this PR adds detection of "simple" vs "complex" non-common operands. FURB108 now only fires when the operands that would be placed in the
intuple are simple expressions that are safe for eager evaluation:int,str,bytes,float,complex), names, member access (c.y), unary ops (-x), binary ops (a + b) — all recursively simpleIndexExpr(events[i]),CallExpr(f()) — may raise exceptions or have side effectsThis is the "detect simple vs complex" approach dosisod described, which preserves FURB108 for the common case (comparing against constant values) while avoiding false positives on guard patterns.
Test plan
IndexExprandCallExproperands (should NOT trigger)cutoff == 0 or events[cutoff - 1] == 0)🤖 Generated with Claude Code