Skip to content

Commit 2d147f5

Browse files
authored
Handle while True loops without break statements (#378)
1 parent e454d2e commit 2d147f5

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 2.14 (2024-12-08)
22

3+
* Handle `while True` loops without `break` statements (kreathon).
34
* Improve reachability analysis (kreathon, #270, #302).
45
* Add type hints for `get_unused_code` and the fields of the `Item` class (John Doknjas, #361).
56

tests/test_reachability.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,13 +731,77 @@ def test_while_true_else(v):
731731
check_unreachable(v, 4, 1, "else")
732732

733733

734+
def test_while_true_no_fall_through(v):
735+
v.scan(
736+
"""\
737+
while True:
738+
raise Exception()
739+
print(":-(")
740+
"""
741+
)
742+
check_unreachable(v, 3, 1, "while")
743+
744+
745+
def test_while_true_no_fall_through_nested(v):
746+
v.scan(
747+
"""\
748+
while True:
749+
if a > 3:
750+
raise Exception()
751+
else:
752+
pass
753+
print(":-(")
754+
"""
755+
)
756+
check_unreachable(v, 6, 1, "while")
757+
758+
759+
def test_while_true_no_fall_through_nested_loops(v):
760+
v.scan(
761+
"""\
762+
while True:
763+
for _ in range(3):
764+
break
765+
while False:
766+
break
767+
print(":-(")
768+
"""
769+
)
770+
check_multiple_unreachable(v, [(4, 2, "while"), (6, 1, "while")])
771+
772+
773+
def test_while_true_fall_through(v):
774+
v.scan(
775+
"""\
776+
while True:
777+
break
778+
print(":-)")
779+
"""
780+
)
781+
assert v.unreachable_code == []
782+
783+
784+
def test_while_true_fall_through_nested(v):
785+
v.scan(
786+
"""\
787+
while True:
788+
if a > 3:
789+
raise Exception()
790+
else:
791+
break
792+
print(":-(")
793+
"""
794+
)
795+
assert v.unreachable_code == []
796+
797+
734798
def test_while_fall_through(v):
735799
v.scan(
736800
"""\
737801
def foo(a):
738802
while a > 0:
739803
return 1
740-
print(":-(")
804+
print(":-)")
741805
"""
742806
)
743807
assert v.unreachable_code == []

vulture/reachability.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,36 @@ def __init__(self, report):
88
self._report = report
99
self._no_fall_through_nodes = set()
1010

11+
# Since we visit the children nodes first, we need to maintain a flag
12+
# that indicates if a break statement was seen. When visiting the
13+
# parent (While, For, or AsyncFor), the value is checked and reset.
14+
# Assumes code is valid (break statements only in loops).
15+
self._current_loop_has_break_statement = False
16+
1117
def visit(self, node):
1218
"""When called, all children of this node have already been visited."""
1319
if isinstance(node, (ast.Break, ast.Continue, ast.Return, ast.Raise)):
1420
self._mark_as_no_fall_through(node)
21+
if isinstance(node, ast.Break):
22+
self._current_loop_has_break_statement = True
23+
1524
elif isinstance(
1625
node,
1726
(
1827
ast.Module,
1928
ast.FunctionDef,
2029
ast.AsyncFunctionDef,
21-
ast.For,
22-
ast.AsyncFor,
2330
ast.With,
2431
ast.AsyncWith,
2532
),
2633
):
2734
self._can_fall_through_statements_analysis(node.body)
2835
elif isinstance(node, ast.While):
2936
self._handle_reachability_while(node)
37+
self._current_loop_has_break_statement = False
38+
elif isinstance(node, (ast.For, ast.AsyncFor)):
39+
self._can_fall_through_statements_analysis(node.body)
40+
self._current_loop_has_break_statement = False
3041
elif isinstance(node, ast.If):
3142
self._handle_reachability_if(node)
3243
elif isinstance(node, ast.IfExp):
@@ -162,6 +173,9 @@ def _handle_reachability_while(self, node):
162173
message="unreachable 'else' block",
163174
)
164175

176+
if not self._current_loop_has_break_statement:
177+
self._mark_as_no_fall_through(node)
178+
165179
self._can_fall_through_statements_analysis(node.body)
166180

167181
def _handle_reachability_try(self, node):

0 commit comments

Comments
 (0)