Preserve parentheses around left side of binary expression

<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR fixes an issue where the binary expression formatting removed parentheses around the left hand side of an expression.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I added a new regression test and re-ran the ecosystem check. It brings down the `check-formatter-stability` output from a 3.4MB file down to 900KB. 

<!-- How was it tested? -->
This commit is contained in:
Micha Reiser 2023-06-30 09:52:14 +02:00 committed by GitHub
parent ae25638b0b
commit 9c2a75284b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 19 deletions

View file

@ -1,12 +1,14 @@
use crate::comments::{trailing_comments, trailing_node_comments, Comments};
use crate::expression::binary_like::{BinaryLayout, FormatBinaryLike};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parenthesize,
default_expression_needs_parentheses, is_expression_parenthesized, NeedsParentheses,
Parenthesize,
};
use crate::expression::Parentheses;
use crate::prelude::*;
use crate::FormatNodeRule;
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
use ruff_python_ast::node::AstNode;
use rustpython_parser::ast::{
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
};
@ -44,9 +46,18 @@ impl<'ast> FormatBinaryLike<'ast> for ExprBinOp {
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()> {
let comments = f.context().comments().clone();
let format_inner = format_with(|f| {
let binary_chain: SmallVec<[&ExprBinOp; 4]> =
iter::successors(Some(self), |parent| parent.left.as_bin_op_expr()).collect();
let format_inner = format_with(|f: &mut PyFormatter| {
let source = f.context().contents();
let binary_chain: SmallVec<[&ExprBinOp; 4]> = iter::successors(Some(self), |parent| {
parent.left.as_bin_op_expr().and_then(|bin_expression| {
if is_expression_parenthesized(bin_expression.as_any_node_ref(), source) {
None
} else {
Some(bin_expression)
}
})
})
.collect();
// SAFETY: `binary_chain` is guaranteed not to be empty because it always contains the current expression.
let left_most = binary_chain.last().unwrap();