Skip to content

[PT006] Fix syntax error when unpacking nested tuples in parametrize fixes (#22441)#22464

Merged
ntBre merged 5 commits intoastral-sh:mainfrom
bxff:fix-pt006-clean
Feb 11, 2026
Merged

[PT006] Fix syntax error when unpacking nested tuples in parametrize fixes (#22441)#22464
ntBre merged 5 commits intoastral-sh:mainfrom
bxff:fix-pt006-clean

Conversation

@bxff
Copy link
Contributor

@bxff bxff commented Jan 8, 2026

Summary

Fixes a syntax error bug in the PT006 rule where applying fixes could generate invalid Python code.

Problem: When unpacking nested tuples in pytest.mark.parametrize decorators, Ruff's code generator unparses tuples without outer parentheses at level 0 (e.g., (1, 2) becomes 1, 2 and ((1,),) becomes (1,),). This caused syntax errors like [1, 2,] or [(1,),,] when used as list elements.

Solution: Introduced two helper functions in parametrize.rs:

  • is_parenthesized: Validates if a string is fully enclosed in matching parentheses
  • unparse_expr_in_sequence: Ensures non-empty tuples are always parenthesized when used as sequence elements

Fixes #22441

Test Plan

  • Added comprehensive regression tests to PT006.py covering:

    • Single-element nested empty tuples: ((),)
    • Single-element nested single-value tuples: ((1,),)
    • Single-element nested multi-value tuples: ((1, 2),)
    • Deeply nested structures: (((1,),),)
    • Mixed types: ("hello",,), ([1, 2],,)
  • Verified edge cases with automated snapshot testing:

    • All 47 existing flake8-pytest-style tests continue to pass
    • Updated snapshots: PT006_default.snap, PT006_csv.snap, PT006_list.snap

…fixes (astral-sh#22441)

When fixing PT006, the code generator would unparse tuples without outer parentheses,
causing syntax errors like `[1, 2,]` or `[(1,),,]` in parametrize decorators.

This introduces `is_parenthesized` and `unparse_expr_in_sequence` helper functions
to ensure tuples are properly parenthesized when used as sequence elements.

Added regression tests for nested tuple edge cases including empty tuples,
single-element tuples, and deeply nested structures.
@amyreese amyreese added rule Implementing or modifying a lint rule fixes Related to suggested fixes for violations labels Jan 8, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 8, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@bxff
Copy link
Contributor Author

bxff commented Jan 28, 2026

@AlexWaygood I think this was missed for assignment.

@ntBre ntBre self-requested a review January 28, 2026 17:49
@ntBre
Copy link
Contributor

ntBre commented Jan 28, 2026

Thanks for the ping. I just requested my review, but we're a bit focused on the upcoming 0.15 release this week, so I may not get to this until next week.

@bxff
Copy link
Contributor Author

bxff commented Jan 28, 2026

Totally understandable, thanks for the update and good luck with the 0.15 release! :)

Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! See my inline comment about parenthesized_range. I tried it locally and all of the tests pass, so we either need more tests, or we should reuse the existing function. This otherwise looks reasonable to me.

@bxff
Copy link
Contributor Author

bxff commented Feb 7, 2026

Thanks for checking. I added a regression case that shows why parenthesized_range isn’t sufficient:

@pytest.mark.parametrize(
    ["param"],
    [
        (((1,)),),
    ],
)
def test_single_element_grouped_tuple(param): ...

With parenthesized_range, this gets treated as already parenthesized (because of the extra grouping parens in source), so we skip wrapping. But the generator still outputs 1, for the tuple element, which produces invalid list syntax when inserted as a list element. This new test fails with the parenthesized_range change and passes with the current string-based is_parenthesized check. I updated the PT006 snapshots accordingly.

@bxff bxff requested a review from ntBre February 9, 2026 10:36
@ntBre
Copy link
Contributor

ntBre commented Feb 11, 2026

Thanks! I took another look at this today, and I think a better solution is just to avoid the generator entirely and use checker.locator().slice(expr) instead. This will preserve the input parentheses when they're available, and we can add parentheses if tuple.parenthesized is false. I also reverted the unrelated formatting changes in the tests.

@ntBre ntBre merged commit 90c8571 into astral-sh:main Feb 11, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fixes Related to suggested fixes for violations rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix error] PT006 parametrize with single fixture where one of the values is an empty tuple

4 participants