mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:28 +00:00
![]() ## Summary Given a statement like: ```python result = ( f(111111111111111111111111111111111111111111111111111111111111111111111111111111111) + 1 )() ``` When we go to parenthesize the target of the assignment, we use `maybe_parenthesize_expression` with `Parenthesize::IfBreaks`. This then checks if the call on the right-hand side needs to be parenthesized, the implementation of which looks like: ```rust impl NeedsParentheses for ExprCall { fn needs_parentheses( &self, _parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { if CallChainLayout::from_expression(self.into(), context.source()) == CallChainLayout::Fluent { OptionalParentheses::Multiline } else if context.comments().has_dangling(self) { OptionalParentheses::Always } else { self.func.needs_parentheses(self.into(), context) } } } ``` Checking for `self.func.needs_parentheses(self.into(), context)` is problematic, since, as in the example above, `self.func` may _already_ be parenthesized -- in which case, we _don't_ want to parenthesize the entire expression. If we do, we end up with this non-ideal formatting: ```python result = ( ( f( 111111111111111111111111111111111111111111111111111111111111111111111111111111111 ) + 1 )() ) ``` This PR modifies the `NeedsParentheses` implementations for call chain expressions to return `Never` if the inner expression has its own parentheses, in which case, the formatting implementations for those expressions will preserve them anyway. Closes https://github.com/astral-sh/ruff/issues/7370. ## Test Plan Zulip improves a bit, everything else is unchanged. Before: | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99981 | 2760 | 40 | | transformers | 0.99944 | 2587 | 413 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99983 | 3496 | 18 | | warehouse | 0.99834 | 648 | 20 | | zulip | 0.99956 | 1437 | 23 | After: | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99981 | 2760 | 40 | | transformers | 0.99944 | 2587 | 413 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99983 | 3496 | 18 | | warehouse | 0.99834 | 648 | 20 | | **zulip** | **0.99962** | **1437** | **22** | |
||
---|---|---|
.. | ||
black_compatibility@conditional_expression.py.snap | ||
black_compatibility@miscellaneous__blackd_diff.py.snap | ||
black_compatibility@miscellaneous__debug_visitor.py.snap | ||
black_compatibility@miscellaneous__decorators.py.snap | ||
black_compatibility@miscellaneous__docstring_no_string_normalization.py.snap | ||
black_compatibility@miscellaneous__docstring_preview_no_string_normalization.py.snap | ||
black_compatibility@miscellaneous__force_pyi.py.snap | ||
black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap | ||
black_compatibility@miscellaneous__power_op_newline.py.snap | ||
black_compatibility@miscellaneous__string_quotes.py.snap | ||
black_compatibility@py_38__pep_572_remove_parens.py.snap | ||
black_compatibility@py_39__python39.py.snap | ||
black_compatibility@py_310__pattern_matching_extras.py.snap | ||
black_compatibility@py_310__pattern_matching_style.py.snap | ||
black_compatibility@py_310__pep_572_py310.py.snap | ||
black_compatibility@simple_cases__attribute_access_on_number_literals.py.snap | ||
black_compatibility@simple_cases__comment_after_escaped_newline.py.snap | ||
black_compatibility@simple_cases__comments2.py.snap | ||
black_compatibility@simple_cases__comments6.py.snap | ||
black_compatibility@simple_cases__comments9.py.snap | ||
black_compatibility@simple_cases__composition.py.snap | ||
black_compatibility@simple_cases__composition_no_trailing_comma.py.snap | ||
black_compatibility@simple_cases__docstring_preview.py.snap | ||
black_compatibility@simple_cases__expression.py.snap | ||
black_compatibility@simple_cases__fmtonoff.py.snap | ||
black_compatibility@simple_cases__fmtonoff4.py.snap | ||
black_compatibility@simple_cases__fmtonoff5.py.snap | ||
black_compatibility@simple_cases__fmtpass_imports.py.snap | ||
black_compatibility@simple_cases__fmtskip5.py.snap | ||
black_compatibility@simple_cases__function.py.snap | ||
black_compatibility@simple_cases__function2.py.snap | ||
black_compatibility@simple_cases__ignore_pyi.py.snap | ||
black_compatibility@simple_cases__multiline_consecutive_open_parentheses_ignore.py.snap | ||
black_compatibility@simple_cases__remove_await_parens.py.snap | ||
black_compatibility@simple_cases__remove_except_parens.py.snap | ||
black_compatibility@simple_cases__remove_for_brackets.py.snap | ||
black_compatibility@simple_cases__return_annotation_brackets.py.snap | ||
black_compatibility@simple_cases__torture.py.snap | ||
black_compatibility@simple_cases__trailing_comma_optional_parens3.py.snap | ||
black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap | ||
black_compatibility@simple_cases__tupleassign.py.snap | ||
black_compatibility@simple_cases__whitespace.py.snap | ||
format@carriage_return__string.py.snap | ||
format@docstring.py.snap | ||
format@expression__annotated_assign.py.snap | ||
format@expression__attribute.py.snap | ||
format@expression__binary.py.snap | ||
format@expression__binary_implicit_string.py.snap | ||
format@expression__boolean_operation.py.snap | ||
format@expression__bytes.py.snap | ||
format@expression__call.py.snap | ||
format@expression__compare.py.snap | ||
format@expression__dict.py.snap | ||
format@expression__dict_comp.py.snap | ||
format@expression__fstring.py.snap | ||
format@expression__generator_exp.py.snap | ||
format@expression__if.py.snap | ||
format@expression__lambda.py.snap | ||
format@expression__list.py.snap | ||
format@expression__list_comp.py.snap | ||
format@expression__named_expr.py.snap | ||
format@expression__set_comp.py.snap | ||
format@expression__slice.py.snap | ||
format@expression__split_empty_brackets.py.snap | ||
format@expression__starred.py.snap | ||
format@expression__string.py.snap | ||
format@expression__subscript.py.snap | ||
format@expression__tuple.py.snap | ||
format@expression__unary.py.snap | ||
format@expression__unsplittable.py.snap | ||
format@expression__yield.py.snap | ||
format@expression__yield_from.py.snap | ||
format@fmt_on_off__comments.py.snap | ||
format@fmt_on_off__empty_file.py.snap | ||
format@fmt_on_off__fmt_off_docstring.py.snap | ||
format@fmt_on_off__form_feed.py.snap | ||
format@fmt_on_off__indent.py.snap | ||
format@fmt_on_off__last_statement.py.snap | ||
format@fmt_on_off__mixed_space_and_tab.py.snap | ||
format@fmt_on_off__newlines.py.snap | ||
format@fmt_on_off__no_fmt_on.py.snap | ||
format@fmt_on_off__off_on_off_on.py.snap | ||
format@fmt_on_off__simple.py.snap | ||
format@fmt_on_off__trailing_comments.py.snap | ||
format@fmt_on_off__yapf.py.snap | ||
format@fmt_skip__decorators.py.snap | ||
format@fmt_skip__docstrings.py.snap | ||
format@fmt_skip__match.py.snap | ||
format@fmt_skip__or_else.py.snap | ||
format@fmt_skip__parentheses.py.snap | ||
format@fmt_skip__type_params.py.snap | ||
format@module_dangling_comment1.py.snap | ||
format@module_dangling_comment2.py.snap | ||
format@newlines.py.snap | ||
format@parentheses__call_chains.py.snap | ||
format@parentheses__nested.py.snap | ||
format@parentheses__opening_parentheses_comment_empty.py.snap | ||
format@parentheses__opening_parentheses_comment_value.py.snap | ||
format@skip_magic_trailing_comma.py.snap | ||
format@statement__ann_assign.py.snap | ||
format@statement__assert.py.snap | ||
format@statement__assign.py.snap | ||
format@statement__aug_assign.py.snap | ||
format@statement__break.py.snap | ||
format@statement__class_definition.py.snap | ||
format@statement__delete.py.snap | ||
format@statement__ellipsis.pyi.snap | ||
format@statement__for.py.snap | ||
format@statement__function.py.snap | ||
format@statement__global.py.snap | ||
format@statement__if.py.snap | ||
format@statement__import.py.snap | ||
format@statement__import_from.py.snap | ||
format@statement__match.py.snap | ||
format@statement__nonlocal.py.snap | ||
format@statement__raise.py.snap | ||
format@statement__return.py.snap | ||
format@statement__return_annotation.py.snap | ||
format@statement__top_level.py.snap | ||
format@statement__top_level.pyi.snap | ||
format@statement__try.py.snap | ||
format@statement__type_alias.py.snap | ||
format@statement__while.py.snap | ||
format@statement__with.py.snap | ||
format@stub_files__comments.pyi.snap | ||
format@stub_files__nesting.pyi.snap | ||
format@stub_files__suite.pyi.snap | ||
format@stub_files__top_level.pyi.snap | ||
format@tab_width.py.snap | ||
format@trailing_comments.py.snap | ||
format@trivia.py.snap |