Honour 'pragma: no mutate block' on else/except/finally arms - #559
Merged
nicklafleur merged 2 commits intoSep 5, 2026
Merged
Conversation
_visit_compound_header is only reachable from visit_If/For/While/With/ Try/FunctionDef/ClassDef/Match. In libcst, else, except, except* and finally are separate nodes owning their own suite, so a pragma on their header line reached no visitor and was silently dropped -- while elif, which parses as a nested If, worked. try/except* parses as TryStar, so the pragma on its try line was dropped too. README documents the block pragma as working on 'any compound statement -- functions, classes, if/elif/else, loops, context managers, etc.' Match/MatchCase is left alone; boxed#554 is fixing that.
nicklafleur
requested changes
Sep 3, 2026
nicklafleur
left a comment
Collaborator
There was a problem hiding this comment.
one very minor nit otherwise lgtm
The parametrized cases were escaped one-line strings while every other test in the file writes its source as a triple-quoted block with real newlines and indentation. Match the surrounding style so the pragma placement is readable at a glance. No behaviour change.
nicklafleur
approved these changes
Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_visit_compound_headeris only reachable fromvisit_If/For/While/With/Try/FunctionDef/ClassDef/Match. In libcst,else,except,except*andfinallyare separate nodes owning their own suite, so a# pragma: no mutate blockon one of those header lines reaches no visitor and is dropped — silently, which is the bad part: you mark an error branch as skipped and it gets mutated anyway.Measured on
main,ignore_node_linesfor a pragma on each header:elifworks because it parses as a nestedIf; theelseone line below it does not. The last row is the same source line behaving differently depending on whether a later handler writesexceptorexcept*—try/except*parses asTryStar, and there is novisit_TryStar.README:291 says the block pragma "works on any compound statement -- functions, classes,
if/elif/else, loops, context managers, etc."; README:345 extends it tofor/else,while/elseandtry/except/finally.Five visitors added, in the existing style.
Match/MatchCasedeliberately untouched — #554 is fixing that, and I did not want to duplicate it. I cherry-picked this commit ontopull/554/head: it auto-merges with zero conflicts and the combined tree passes 50/50 intest_pragma_handling.py. Land them in either order.Checked by reverting: with
pragma_handling.pyback atmainand the tests kept, exactly the 8 new tests fail and the other 36 pass. Adding onlyvisit_Elsestill fails 4 of them (except,except*,finally,TryStar), so each visitor is pinned rather than carried by its neighbours. A sibling-leak case asserts the statement after theelseblock stays mutable.uv run pytestgives 387 passed / 2 failed against 379 passed / 2 failed onmain, same venv afteruv sync(three consecutive runs each). The two failures are thetest_e2e_type_checkingsnapshot tests and are identical on both trees.test_safe_setproctitleis flaky here on macOS — it failed in one earlier run of each tree and in none of the three above.ruff checkandruff formatclean at the pinned 0.15.7;mypyreports 19 errors, all in__main__.py, none in the files touched, identical tomain. I did not run./scripts/run_tests.shin the Linux container.Found by enumerating the
visit_*methods against the libcst node types that own a suite, then probing each. AI assistance: this change and its tests were drafted with Claude Opus 5 (claude-opus-5). The commands and numbers above were run locally.