Skip to content

Commit 896d8d7

Browse files
aauschclaude
andcommitted
Python: add query tests for unparenthesized except chains of every length
The parser test added with the fix pins the AST. These pin the behaviour a user actually sees, through the real extraction path including the tree-sitter fallback, and they cover chains longer than two. Chains of three or more behave differently from chains of two, which is worth having written down. `except A, B, C:` fails the default parser outright, so `Module.py_ast` falls back to tree-sitter and the result is already correct. `except A, B:` parses successfully under the Python 2 reading, so the fallback never fires and the bad AST reaches the queries. That is why only the two-type form produced a false positive. It also means the two cases cannot share a file: any three-type clause sends the whole file to tree-sitter and masks the two-type behaviour. Hence relaxed_except.py and relaxed_except_long.py, with a comment in each saying so. Each name is used in exactly one clause for the same reason -- a name reused in a parenthesized clause is a use regardless, and hides the defect. Verified by reverting the extractor fix in a 2.26.3 bundle: relaxed_except.py then reports `Import of 'Beta' is not used.` and the test fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 66573bb commit 896d8d7

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

python/ql/test/query-tests/Imports/unused/UnusedImport.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
| imports_test.py:27:1:27:25 | Import | Import of 'func2' is not used. |
77
| imports_test.py:34:1:34:14 | Import | Import of 'module2' is not used. |
88
| imports_test.py:116:1:116:41 | Import | Import of 'not_a_fixture' is not used. |
9+
| relaxed_except.py:12:1:12:68 | Import | Import of 'NeverUsed' is not used. |
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# PEP 758 allows unparenthesized exception types when there is no `as` clause.
2+
# Every name below is used as an exception type, so no import here is unused.
3+
# `NeverUsed` is imported and never used, and is the one expected result.
4+
#
5+
# Each name appears in exactly one clause on purpose: a name that also appeared
6+
# in a parenthesized clause would be a use regardless, and would mask the
7+
# behaviour under test.
8+
#
9+
# This file deliberately contains no `except A, B, C:` clause. Three or more
10+
# unparenthesized types fail the default parser, which sends the whole file to
11+
# the tree-sitter parser and would likewise mask it.
12+
from relaxed_except_defs import Alpha, Beta, Delta, Gamma, NeverUsed
13+
14+
15+
def unparenthesized():
16+
try:
17+
pass
18+
except Alpha, Beta:
19+
raise
20+
21+
22+
def parenthesized():
23+
try:
24+
pass
25+
except (Gamma, Delta):
26+
raise
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class Alpha(Exception):
2+
pass
3+
4+
5+
class Beta(Exception):
6+
pass
7+
8+
9+
class Gamma(Exception):
10+
pass
11+
12+
13+
class Delta(Exception):
14+
pass
15+
16+
17+
class Epsilon(Exception):
18+
pass
19+
20+
21+
class NeverUsed(Exception):
22+
pass
23+
24+
25+
class Zeta(Exception):
26+
pass
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Three or more unparenthesized exception types. These fail the default parser
2+
# and are extracted by the tree-sitter parser instead; all names are still uses.
3+
from relaxed_except_defs import Delta, Epsilon, Gamma, Zeta
4+
5+
6+
def three():
7+
try:
8+
pass
9+
except Gamma, Delta, Epsilon:
10+
raise
11+
12+
13+
def four():
14+
try:
15+
pass
16+
except Gamma, Delta, Epsilon, Zeta:
17+
raise

0 commit comments

Comments
 (0)