fix: WPS457 false positive when while True is inside try/except#3604
fix: WPS457 false positive when while True is inside try/except#3604stakeswky wants to merge 6 commits intowemake-services:masterfrom
while True is inside try/except#3604Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3604 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 368 368
Lines 12291 12302 +11
Branches 853 854 +1
=========================================
+ Hits 12291 12302 +11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
sobolevn
left a comment
There was a problem hiding this comment.
Thanks! If you are using AI, please, double check the produced results :)
|
|
||
| # Correct: while inside try/except | ||
|
|
||
| correct_while8 = """ |
There was a problem hiding this comment.
Please, move correct examples to correct examples' section.
There was a problem hiding this comment.
Done — moved correct examples up to the correct section.
| pass | ||
| """ | ||
|
|
||
| correct_while9 = """ |
There was a problem hiding this comment.
All these examples are the same from the AST's point of view.
Please, remove duplicates.
There was a problem hiding this comment.
Good point. Removed the duplicates — kept one simple case (correct_while9) and replaced the other with a nested variant (correct_while10) so they're structurally distinct.
|
|
||
| def is_in_try_except(node: ast.AST) -> bool: | ||
| """Checks whether a node is directly inside a ``try/except`` block.""" | ||
| parent = get_parent(node) |
There was a problem hiding this comment.
Why not using get_closest_parent()?
There was a problem hiding this comment.
Refactored to use get_closest_parent() — much cleaner. Thanks for the pointer.
| """ | ||
|
|
||
| correct_while10 = """ | ||
| try: |
There was a problem hiding this comment.
Please, add more nesting in the tests. But, keep them minimal.
There was a problem hiding this comment.
Added correct_while10 with nested try/except inside the while loop to cover deeper nesting, kept it minimal.
- Replace manual while-loop in is_in_try_except() with get_closest_parent() to find the nearest Try or scope-boundary parent - Move correct_while8/9/10 to the correct examples section - Remove duplicate correct_while9 (same AST as correct_while8) - Add correct_while10 with nested try/except for more nesting coverage
stakeswky
left a comment
There was a problem hiding this comment.
Thanks for the detailed review! I've addressed all four points:
-
get_closest_parent(): Refactoredis_in_try_except()to usewalk.get_closest_parent(node, (*_SCOPE_BOUNDARIES, ast.Try))— finds the nearestTryor scope-boundary parent in one call, then checks if it's aTrywith handlers. -
Correct examples section: Moved
correct_while8/9/10above the# Do raise:section where they belong. -
Removed duplicate:
correct_while9(same AST ascorrect_while8— both aredef fn(): try: while True: yield; except: pass) has been removed and replaced with a simpler top-leveltry/exceptcase. -
More nesting: Added
correct_while10with a nestedtry/exceptinside thewhile Truebody, inside an outertry/except.
| try: | ||
| while True: | ||
| yield some() | ||
| except StopIteration: | ||
| pass |
There was a problem hiding this comment.
| try: | |
| while True: | |
| yield some() | |
| except StopIteration: | |
| pass | |
| try: | |
| if some: | |
| while True: | |
| yield some | |
| except StopIteration: | |
| pass |
There was a problem hiding this comment.
Applied your suggestion — replaced with try: if some: while True: yield some; except StopIteration: pass to test deeper nesting.
| from wemake_python_styleguide.logic.walk import tree as walk | ||
| from wemake_python_styleguide.types import AnyLoop, AnyNodes | ||
|
|
||
| _SCOPE_BOUNDARIES = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef) |
There was a problem hiding this comment.
You don't need a variable for a single use of values.
There was a problem hiding this comment.
Done — removed _SCOPE_BOUNDARIES and inlined the tuple directly in the get_closest_parent() call.
| handle() | ||
| """ | ||
|
|
||
| correct_while10 = """ |
There was a problem hiding this comment.
you never test try being further in hierarchy then scope boundaries.
There was a problem hiding this comment.
Added wrong_while4: a while True inside a function that is itself inside a try/except — the scope boundary (function def) should block the try from exempting the loop, so it raises InfiniteWhileLoopViolation. Included in test_wrong_while_loops_with_try.
- Remove _SCOPE_BOUNDARIES variable, inline tuple directly in get_closest_parent() call - Replace correct_while8 with sobolevn's suggestion: try/if/while nesting - Add wrong_while4: try/except outside a function boundary should not exempt the inner while - Include wrong_while4 in test_wrong_while_loops_with_try parametrize
| handle() | ||
| """ | ||
|
|
||
| # Wrong: try/except is outside a scope boundary, should not exempt the while |
Fixes #3603
Problem
WPS457(InfiniteWhileLoopViolation) was incorrectly triggered when awhile Trueloop was wrapped in atry/exceptblock:This is not an infinite loop — the
excepthandler provides a clear termination path. The previous implementation only checked forbreak/raise/return/ExceptHandlerinside thewhilebody, but missed the case where the enclosingtry/exceptitself acts as the exit mechanism.Fix
Added
is_in_try_except()helper inwemake_python_styleguide/logic/tree/loops.pythat walks up the AST parent chain to detect if thewhilenode is directly inside atryblock that has at least oneexcepthandler.In
_check_infinite_while_loop, ifis_in_try_except()returnsTrue, the violation is suppressed.Note: A
try/finallywithoutexceptstill triggers WPS457, sincefinallydoes not catch exceptions and cannot terminate the loop.Tests
Added three new correct cases (
correct_while8/9/10) covering the reported pattern, and one new wrong case (wrong_while3) fortry/finallywithoutexcept.