Skip to content

feat: support unpacking in Python comprehensions - #19

Merged
exKAZUu merged 12 commits into
mainfrom
feat/unpacking-comprehensions
Oct 18, 2026
Merged

exKAZUu merged 12 commits into
mainfrom
feat/unpacking-comprehensions

Conversation

@exKAZUu

@exKAZUu exKAZUu commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Why

Python 3.15 permits unpacking comprehension bodies, but tuple operands and dictionary unpacking produce errors or incorrectly grouped trees. This addresses the feature proposed in tree-sitter/tree-sitter-python#353, addressing tree-sitter/tree-sitter-python#352 and the accepted PEP 798.

Requirements

  • required: Improve the fork using eligible upstream issues and PRs, without preserving backward compatibility.
  • required: Check existing PRs and keep a dedicated PR for each identified upstream issue or PR.
  • required: Run review-booster and complete-pr concurrently, then release the changes.
  • chosen: Support PEP 798 in all four comprehension forms with existing public splat node names and Python's operand restrictions; allow valid unpacking inside tuple displays, calls, and subscripts.
  • chosen: Remove pattern splats from general primary expressions and permit unpacking explicitly at its legal expression and type boundaries. A comprehension-only direct-operand restriction still admitted recursive invalid splats through operators and parentheses; duplicating every recursive expression rule would increase complexity. This shared repair also corrects list-literal operand grouping and requires commas in nonempty tuples.
  • chosen: Call, subscript, generator-comprehension, and dictionary-comprehension unpacking accept ordinary expressions, including conditionals and lambdas, but require parentheses around walrus operands. Their shared operand rule excludes bare named expressions and is also used inside boolean, not, conditional, and lambda expressions, because filtering only the direct operand permits recursive bypasses. Collection displays, list/set comprehensions, comma-separated subjects and expression lists use primary expressions. CPython 3.15 distinguishes generator and dictionary comprehensions from list/set comprehensions; one uniform comprehension operand rule was incorrect.
  • chosen: Keep generated tables and corpus expectations synchronized with the fork's generator and runtime.
  • chosen: Match subjects explicitly permit starred elements only in comma-separated subjects, preserving subject fields and rejecting a lone starred subject; this restores a legal boundary lost when pattern splats were removed from primary expressions.
  • chosen: Top-level starred types are restricted by context: declaration type parameters use *NAME or **NAME, generic arguments use ordinary starred expressions, and only *args annotations admit primary-expression starred operands. General annotations and alias right-hand sides exclude splats. Separate declaration and argument boundaries, including a declaration-specific alias name, prevent widening one operand from admitting invalid types elsewhere; this necessarily also disambiguates ordinary type(...) assignments.

Customer Summary

List, set, dictionary, and generator comprehensions accept unpacking bodies, including tuple and call operands, async clauses, and filters. Invalid recursive splats and unparenthesized conditional display and list/set-comprehension operands produce parse errors. List literal splats contain their complete attribute or call operand. Single-element tuples containing unpacking require a trailing comma.

Technical Summary

Splats are explicit at collection, argument, subscript, expression-list, print-statement, match-subject, and type boundaries rather than general primary expressions. This prevents operators and parentheses from admitting invalid nested splats without duplicating the expression grammar. Tuple displays require a comma; parenthesized expressions remain separate. Call, subscript, generator-comprehension, and dictionary-comprehension unpacking retain conditional and lambda operands while rejecting unparenthesized walrus operands, including recursive operator and lambda variants. Starred types are confined to declaration parameters, generic arguments, and variadic positional annotations with their respective operand restrictions. Type aliases use declaration names, so ordinary type(...) assignments remain expressions. Starred types appear directly as splat_type nodes at their permitted boundaries. The compact parser and node metadata are regenerated, and the README identifies this fork's grammar, scanner, and highlighting changes.

Corpus coverage includes all four comprehension forms, parenthesized conditional operands, valid unpacking inside operands, expression lists, subscripts, starred match subjects, and independent rejection cases for direct and recursive invalid splats. The rejection cases use :error without a recorded tree, since the runner only checks that they fail to parse. Existing list-literal expectations assert correct grouping; invalid tuple/yield examples use valid syntax, with their invalid counterparts covered separately.

Testing

The initial four comprehension cases reproduced failures against the base parser. Recursive invalid-splat reproducers parsed without errors before the repair. TREE_SITTER_ITERATIONS=100 TREE_SITTER_EDITS=10 mise exec rust@1.99.1 -- bun wb verify --full test/unit/corpus.test.ts test/unit/incremental.test.ts passes, including linting, type checking, the native corpus, and incremental parsing. The pinned CPython Lib/test/test_typing.py file that failed in CI also parses successfully after restoring explicit starred type operands. Manual comparisons against CPython 3.15.0rc2 cover 192 expression-unpacking cases and 143 typing cases with no acceptance mismatches. print >>sys.stderr, *a, which CPython compiles as an expression list, parses as a print statement with a list_splat argument, as on main. The full cross-platform, Wasm, and sanitizer suites run in PR CI.

Notes

The shared expression repair is necessary because restricting only the direct comprehension operand leaves invalid nested splats reachable through operators and parentheses.

The generated parser contains generator-emitted trailing spaces and remains byte-compatible with regeneration. Code-gauge reports existing violations in scanner and example files; none are in code changed here.

exKAZUu and others added 12 commits October 18, 2026 01:20
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
…ions

Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
Python 3 reads `print >>sys.stderr, *a` as an expression list whose
second element unpacks `a`, so the print statement arguments accept
the same splats as expression lists.

Co-authored-by: WillBooster (Claude Code) <agent@willbooster.com>
`:error` cases assert only that the input fails to parse, and the test
runner never compares their trees, so the placeholder trees documented
nothing.

Co-authored-by: WillBooster (Claude Code) <agent@willbooster.com>
@exKAZUu exKAZUu self-assigned this Oct 18, 2026
@exKAZUu
exKAZUu merged commit a65a8a5 into main Oct 18, 2026
10 checks passed
@exKAZUu
exKAZUu deleted the feat/unpacking-comprehensions branch October 18, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant