Skip to content

Commit 66573bb

Browse files
aauschclaude
andcommitted
Python: fix PEP 758 except A, B: in the default parser
The grammar rule shared by both readings is except_clause: 'except' [test [(',' | 'as') test]] and `visit_except_clause` ignored the separator token, always treating the fourth child as an alias to bind. So `except A, B:` extracted `B` as a Store rather than a use, which is the Python 2 reading. Queries that reason about whether a name is used then report false positives; `py/unused-import` flags the import of `B` as unused. The tree-sitter parser already extracts this as a tuple of exception types (#20990), so the two parsers disagreed. `tests/parser/exceptions_relaxed.py` is an unsuffixed parser test, which asserts the two parsers produce identical ASTs; it fails without this change. With the fix, the default parser reproduces the existing `tests/parser/exceptions_new.expected` byte for byte, and of the 37 parser test files only the two containing PEP 758 syntax change at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d24ab5e commit 66573bb

3 files changed

Lines changed: 24 additions & 1 deletion

File tree

python/extractor/semmle/python/parser/ast.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,16 @@ def visit_except_clause(self, node):
981981
if len(node.children) > 1:
982982
type = self.visit(node.children[1], LOAD)
983983
if len(node.children) > 3:
984-
name = self.visit(node.children[3], STORE)
984+
if is_token(node.children[2], "as"):
985+
name = self.visit(node.children[3], STORE)
986+
else:
987+
# PEP 758 (Python 3.14+): `except A, B:` is an unparenthesized
988+
# tuple of exception types, not a Python 2 alias binding. The
989+
# grammar rule `'except' [test [(',' | 'as') test]]` is shared
990+
# between both readings, so the separator token decides.
991+
elts = [type, self.visit(node.children[3], LOAD)]
992+
type = ast.Tuple(elts, LOAD)
993+
set_location(type, node.children[1].start, node.children[3].end)
985994
return type, name
986995

987996
def visit_del_stmt(self, node):
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
try:
2+
a
3+
except b, c:
4+
d
5+
except (e, f):
6+
g
7+
except h as i:
8+
j
9+
except k:
10+
l
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: fix
3+
---
4+
* Fixed the extraction of PEP 758 `except A, B:` clauses by the default (non-tree-sitter) Python parser. Previously the second exception type was extracted as a Python 2 style alias binding, so it was recorded as a `Store` rather than a use. This caused false positives from queries that reason about whether a name is used, such as `py/unused-import`.

0 commit comments

Comments
 (0)