mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-23 04:55:09 +00:00
Fix invalid syntax for binary expression in unary op (#5370)
This commit is contained in:
parent
38189ed913
commit
955e9ef821
20 changed files with 200 additions and 116 deletions
|
@ -5,7 +5,7 @@ use rustpython_parser::ast::{self, Expr};
|
|||
|
||||
use ruff_formatter::{format_args, write};
|
||||
|
||||
use crate::expression::parentheses::Parentheses;
|
||||
use crate::expression::parentheses::{is_expression_parenthesized, Parentheses};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Trait to implement a binary like syntax that has a left operand, an operator, and a right operand.
|
||||
|
@ -24,7 +24,7 @@ pub(super) trait FormatBinaryLike<'ast> {
|
|||
let right = self.right()?;
|
||||
|
||||
let layout = if parentheses == Some(Parentheses::Custom) {
|
||||
self.binary_layout()
|
||||
self.binary_layout(f.context().contents())
|
||||
} else {
|
||||
BinaryLayout::Default
|
||||
};
|
||||
|
@ -113,9 +113,9 @@ pub(super) trait FormatBinaryLike<'ast> {
|
|||
}
|
||||
|
||||
/// Determines which binary layout to use.
|
||||
fn binary_layout(&self) -> BinaryLayout {
|
||||
fn binary_layout(&self, source: &str) -> BinaryLayout {
|
||||
if let (Ok(left), Ok(right)) = (self.left(), self.right()) {
|
||||
BinaryLayout::from_left_right(left, right)
|
||||
BinaryLayout::from_left_right(left, right, source)
|
||||
} else {
|
||||
BinaryLayout::Default
|
||||
}
|
||||
|
@ -134,8 +134,8 @@ pub(super) trait FormatBinaryLike<'ast> {
|
|||
fn operator(&self) -> Self::FormatOperator;
|
||||
}
|
||||
|
||||
fn can_break_expr(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
fn can_break_expr(expr: &Expr, source: &str) -> bool {
|
||||
let can_break = match expr {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: expressions, ..
|
||||
})
|
||||
|
@ -153,12 +153,11 @@ fn can_break_expr(expr: &Expr) -> bool {
|
|||
!(args.is_empty() && keywords.is_empty())
|
||||
}
|
||||
Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::GeneratorExp(_) => true,
|
||||
Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => match operand.as_ref() {
|
||||
Expr::BinOp(_) => true,
|
||||
_ => can_break_expr(operand.as_ref()),
|
||||
},
|
||||
Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => can_break_expr(operand.as_ref(), source),
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
can_break || is_expression_parenthesized(expr.into(), source)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
|
@ -206,8 +205,8 @@ pub(super) enum BinaryLayout {
|
|||
}
|
||||
|
||||
impl BinaryLayout {
|
||||
pub(super) fn from_left_right(left: &Expr, right: &Expr) -> BinaryLayout {
|
||||
match (can_break_expr(left), can_break_expr(right)) {
|
||||
pub(super) fn from_left_right(left: &Expr, right: &Expr, source: &str) -> BinaryLayout {
|
||||
match (can_break_expr(left, source), can_break_expr(right, source)) {
|
||||
(false, false) => BinaryLayout::Default,
|
||||
(true, false) => BinaryLayout::ExpandLeft,
|
||||
(false, true) => BinaryLayout::ExpandRight,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::comments::{trailing_comments, Comments};
|
||||
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,
|
||||
|
@ -10,6 +10,8 @@ use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWi
|
|||
use rustpython_parser::ast::{
|
||||
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::iter;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprBinOp {
|
||||
|
@ -40,41 +42,68 @@ impl<'ast> FormatBinaryLike<'ast> for ExprBinOp {
|
|||
type FormatOperator = FormatOwnedWithRule<Operator, FormatOperator, PyFormatContext<'ast>>;
|
||||
|
||||
fn fmt_default(&self, f: &mut PyFormatter<'ast, '_>) -> FormatResult<()> {
|
||||
let ExprBinOp {
|
||||
range: _,
|
||||
left,
|
||||
op,
|
||||
right,
|
||||
} = self;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
let operator_comments = comments.dangling_comments(self);
|
||||
let needs_space = !is_simple_power_expression(self);
|
||||
|
||||
let before_operator_space = if needs_space {
|
||||
soft_line_break_or_space()
|
||||
} else {
|
||||
soft_line_break()
|
||||
};
|
||||
let format_inner = format_with(|f| {
|
||||
let binary_chain: SmallVec<[&ExprBinOp; 4]> =
|
||||
iter::successors(Some(self), |parent| parent.left.as_bin_op_expr()).collect();
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
left.format(),
|
||||
before_operator_space,
|
||||
op.format(),
|
||||
trailing_comments(operator_comments),
|
||||
]
|
||||
)?;
|
||||
// SAFETY: `binary_chain` is guaranteed not to be empty because it always contains the current expression.
|
||||
let left_most = binary_chain.last().unwrap();
|
||||
|
||||
// Format the operator on its own line if the right side has any leading comments.
|
||||
if comments.has_leading_comments(right.as_ref()) {
|
||||
write!(f, [hard_line_break()])?;
|
||||
} else if needs_space {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
// Format the left most expression
|
||||
group(&left_most.left.format()).fmt(f)?;
|
||||
|
||||
write!(f, [group(&right.format())])
|
||||
// Iterate upwards in the binary expression tree and, for each level, format the operator
|
||||
// and the right expression.
|
||||
for current in binary_chain.into_iter().rev() {
|
||||
let ExprBinOp {
|
||||
range: _,
|
||||
left: _,
|
||||
op,
|
||||
right,
|
||||
} = current;
|
||||
|
||||
let operator_comments = comments.dangling_comments(current);
|
||||
let needs_space = !is_simple_power_expression(current);
|
||||
|
||||
let before_operator_space = if needs_space {
|
||||
soft_line_break_or_space()
|
||||
} else {
|
||||
soft_line_break()
|
||||
};
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
before_operator_space,
|
||||
op.format(),
|
||||
trailing_comments(operator_comments),
|
||||
]
|
||||
)?;
|
||||
|
||||
// Format the operator on its own line if the right side has any leading comments.
|
||||
if comments.has_leading_comments(right.as_ref()) || !operator_comments.is_empty() {
|
||||
hard_line_break().fmt(f)?;
|
||||
} else if needs_space {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
|
||||
group(&right.format()).fmt(f)?;
|
||||
|
||||
// It's necessary to format the trailing comments because the code bypasses
|
||||
// `FormatNodeRule::fmt` for the nested binary expressions.
|
||||
// Don't call the formatting function for the most outer binary expression because
|
||||
// these comments have already been formatted.
|
||||
if current != self {
|
||||
trailing_node_comments(current).fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
group(&format_inner).fmt(f)
|
||||
}
|
||||
|
||||
fn left(&self) -> FormatResult<&Expr> {
|
||||
|
@ -162,7 +191,7 @@ impl NeedsParentheses for ExprBinOp {
|
|||
) -> Parentheses {
|
||||
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
|
||||
Parentheses::Optional => {
|
||||
if self.binary_layout() == BinaryLayout::Default
|
||||
if self.binary_layout(source) == BinaryLayout::Default
|
||||
|| comments.has_leading_comments(self.right.as_ref())
|
||||
|| comments.has_dangling_comments(self)
|
||||
{
|
||||
|
|
|
@ -31,9 +31,9 @@ impl FormatNodeRule<ExprBoolOp> for FormatExprBoolOp {
|
|||
impl<'ast> FormatBinaryLike<'ast> for ExprBoolOp {
|
||||
type FormatOperator = FormatOwnedWithRule<BoolOp, FormatBoolOp, PyFormatContext<'ast>>;
|
||||
|
||||
fn binary_layout(&self) -> BinaryLayout {
|
||||
fn binary_layout(&self, source: &str) -> BinaryLayout {
|
||||
match self.values.as_slice() {
|
||||
[left, right] => BinaryLayout::from_left_right(left, right),
|
||||
[left, right] => BinaryLayout::from_left_right(left, right, source),
|
||||
[..] => BinaryLayout::Default,
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ impl NeedsParentheses for ExprBoolOp {
|
|||
comments: &Comments,
|
||||
) -> Parentheses {
|
||||
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
|
||||
Parentheses::Optional => match self.binary_layout() {
|
||||
Parentheses::Optional => match self.binary_layout(source) {
|
||||
BinaryLayout::Default => Parentheses::Optional,
|
||||
|
||||
BinaryLayout::ExpandRight
|
||||
|
|
|
@ -34,10 +34,10 @@ impl FormatNodeRule<ExprCompare> for FormatExprCompare {
|
|||
impl<'ast> FormatBinaryLike<'ast> for ExprCompare {
|
||||
type FormatOperator = FormatOwnedWithRule<CmpOp, FormatCmpOp, PyFormatContext<'ast>>;
|
||||
|
||||
fn binary_layout(&self) -> BinaryLayout {
|
||||
fn binary_layout(&self, source: &str) -> BinaryLayout {
|
||||
if self.ops.len() == 1 {
|
||||
match self.comparators.as_slice() {
|
||||
[right] => BinaryLayout::from_left_right(&self.left, right),
|
||||
[right] => BinaryLayout::from_left_right(&self.left, right, source),
|
||||
[..] => BinaryLayout::Default,
|
||||
}
|
||||
} else {
|
||||
|
@ -102,7 +102,7 @@ impl NeedsParentheses for ExprCompare {
|
|||
comments: &Comments,
|
||||
) -> Parentheses {
|
||||
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
|
||||
parentheses @ Parentheses::Optional => match self.binary_layout() {
|
||||
parentheses @ Parentheses::Optional => match self.binary_layout(source) {
|
||||
BinaryLayout::Default => parentheses,
|
||||
|
||||
BinaryLayout::ExpandRight
|
||||
|
|
|
@ -10,7 +10,7 @@ use ruff_formatter::{
|
|||
};
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
mod binary_like;
|
||||
pub(crate) mod binary_like;
|
||||
pub(crate) mod expr_attribute;
|
||||
pub(crate) mod expr_await;
|
||||
pub(crate) mod expr_bin_op;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue