From 2380bfd459196551b1e40df28fb650c5712d657f Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 8 Dec 2025 17:09:40 +0000 Subject: [PATCH] Python: Add support for PEP-758 exception syntax See https://peps.python.org/pep-0758/ for more details. We implement this by extending the syntax for exceptions and exception groups so that the `type` field can now contain either an expression (which matches the old behaviour), or a comma-separated list of at least two elements (representing the new behaviour). We model the latter case using a new node type `exception_list`, which in `tsg-python` is simply mapped to a tuple. This means it matches the existing behaviour (when the tuple is surrounded by parentheses) exactly, hence we don't need to change any other code. As a consequence of this, however, we cannot directly parse the Python 2.7 syntax `except Foo, e: ...` as `except Foo as e: ...`, as this would introduce an ambiguity in the grammar. Thus, we have removed support for the (deprecated) 2.7-style syntax, and only allow `as` to indicate binding of the exception. The syntax `except Foo, e: ...` continues to be parsed (in particular, it's not suddenly a syntax error), but it will be parsed as if it were `except (Foo, e): ...`, which may not give the correct results. In principle we could extend the QL libraries to account for this case (specifically when analysing Python 2 code). In practice, however, I expect this to have a minor impact on results, and not worth the additional investment at this time. --- python/extractor/tsg-python/python.tsg | 6 +++++- python/extractor/tsg-python/tsp/grammar.js | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/python/extractor/tsg-python/python.tsg b/python/extractor/tsg-python/python.tsg index 8cd3bc50743..dd11814753d 100644 --- a/python/extractor/tsg-python/python.tsg +++ b/python/extractor/tsg-python/python.tsg @@ -12,7 +12,7 @@ (assignment !type) @assign { let @assign.node = (ast-node @assign "Assign") } -[ (expression_list) (tuple) (tuple_pattern) (pattern_list) (index_expression_list) ] @tuple +[ (expression_list) (tuple) (tuple_pattern) (pattern_list) (index_expression_list) (exception_list)] @tuple { let @tuple.node = (ast-node @tuple "Tuple") } (list_pattern) @list @@ -3445,6 +3445,9 @@ (tuple element: (_) @elt) @parent (tuple_pattern element: (_) @elt) @parent + + ; An exception list, as in `except A, B, C: ...` + (exception_list element: (_) @elt) @parent ] { edge @parent.node -> @elt.node @@ -3480,6 +3483,7 @@ (parenthesized_expression inner: (_) @elt) (set element: (_) @elt) (match_sequence_pattern (_) @elt) + (exception_list element: (_) @elt) ] @seq { attr (@elt.node) _inherited_ctx = @seq.node diff --git a/python/extractor/tsg-python/tsp/grammar.js b/python/extractor/tsg-python/tsp/grammar.js index 145fa6a4b9a..a30ddb9f0a0 100644 --- a/python/extractor/tsg-python/tsp/grammar.js +++ b/python/extractor/tsg-python/tsp/grammar.js @@ -297,12 +297,21 @@ module.exports = grammar({ ) ), + exception_list: $ => seq( + field('element', $.expression), + repeat1( + seq( + ',', + field('element', $.expression)) + ) + ), + except_clause: $ => seq( 'except', optional(seq( - field('type', $.expression), + field('type', choice($.expression, $.exception_list)), optional(seq( - choice('as', ','), + 'as', field('alias', $.expression) )) )), @@ -314,7 +323,7 @@ module.exports = grammar({ 'except', '*', seq( - field('type', $.expression), + field('type', choice($.expression, $.exception_list)), optional(seq( 'as', field('alias', $.expression)