use std::cmp::Ordering; use std::slice; use ruff_formatter::{ write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions, }; use ruff_python_ast as ast; use ruff_python_ast::parenthesize::parentheses_iterator; use ruff_python_ast::visitor::preorder::{walk_expr, PreorderVisitor}; use ruff_python_ast::{AnyNodeRef, Expr, ExpressionRef, Operator}; use ruff_python_trivia::CommentRanges; use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments}; use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::expr_generator_exp::is_generator_parenthesized; use crate::expression::expr_tuple::is_tuple_parenthesized; use crate::expression::parentheses::{ is_expression_parenthesized, optional_parentheses, parenthesized, HuggingStyle, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, }; use crate::prelude::*; use crate::preview::{ is_hug_parens_with_braces_and_square_brackets_enabled, is_multiline_string_handling_enabled, }; use crate::string::AnyString; mod binary_like; pub(crate) mod expr_attribute; pub(crate) mod expr_await; pub(crate) mod expr_bin_op; pub(crate) mod expr_bool_op; pub(crate) mod expr_boolean_literal; pub(crate) mod expr_bytes_literal; pub(crate) mod expr_call; pub(crate) mod expr_compare; pub(crate) mod expr_dict; pub(crate) mod expr_dict_comp; pub(crate) mod expr_ellipsis_literal; pub(crate) mod expr_f_string; pub(crate) mod expr_generator_exp; pub(crate) mod expr_if_exp; pub(crate) mod expr_ipy_escape_command; pub(crate) mod expr_lambda; pub(crate) mod expr_list; pub(crate) mod expr_list_comp; pub(crate) mod expr_name; pub(crate) mod expr_named_expr; pub(crate) mod expr_none_literal; pub(crate) mod expr_number_literal; pub(crate) mod expr_set; pub(crate) mod expr_set_comp; pub(crate) mod expr_slice; pub(crate) mod expr_starred; pub(crate) mod expr_string_literal; pub(crate) mod expr_subscript; pub(crate) mod expr_tuple; pub(crate) mod expr_unary_op; pub(crate) mod expr_yield; pub(crate) mod expr_yield_from; mod operator; pub(crate) mod parentheses; #[derive(Copy, Clone, PartialEq, Eq, Default)] pub struct FormatExpr { parentheses: Parentheses, } impl FormatRuleWithOptions> for FormatExpr { type Options = Parentheses; fn with_options(mut self, options: Self::Options) -> Self { self.parentheses = options; self } } impl FormatRule> for FormatExpr { fn fmt(&self, expression: &Expr, f: &mut PyFormatter) -> FormatResult<()> { let parentheses = self.parentheses; let format_expr = format_with(|f| match expression { Expr::BoolOp(expr) => expr.format().fmt(f), Expr::NamedExpr(expr) => expr.format().fmt(f), Expr::BinOp(expr) => expr.format().fmt(f), Expr::UnaryOp(expr) => expr.format().fmt(f), Expr::Lambda(expr) => expr.format().fmt(f), Expr::IfExp(expr) => expr.format().fmt(f), Expr::Dict(expr) => expr.format().fmt(f), Expr::Set(expr) => expr.format().fmt(f), Expr::ListComp(expr) => expr.format().fmt(f), Expr::SetComp(expr) => expr.format().fmt(f), Expr::DictComp(expr) => expr.format().fmt(f), Expr::GeneratorExp(expr) => expr.format().fmt(f), Expr::Await(expr) => expr.format().fmt(f), Expr::Yield(expr) => expr.format().fmt(f), Expr::YieldFrom(expr) => expr.format().fmt(f), Expr::Compare(expr) => expr.format().fmt(f), Expr::Call(expr) => expr.format().fmt(f), Expr::FString(expr) => expr.format().fmt(f), Expr::StringLiteral(expr) => expr.format().fmt(f), Expr::BytesLiteral(expr) => expr.format().fmt(f), Expr::NumberLiteral(expr) => expr.format().fmt(f), Expr::BooleanLiteral(expr) => expr.format().fmt(f), Expr::NoneLiteral(expr) => expr.format().fmt(f), Expr::EllipsisLiteral(expr) => expr.format().fmt(f), Expr::Attribute(expr) => expr.format().fmt(f), Expr::Subscript(expr) => expr.format().fmt(f), Expr::Starred(expr) => expr.format().fmt(f), Expr::Name(expr) => expr.format().fmt(f), Expr::List(expr) => expr.format().fmt(f), Expr::Tuple(expr) => expr.format().fmt(f), Expr::Slice(expr) => expr.format().fmt(f), Expr::IpyEscapeCommand(expr) => expr.format().fmt(f), }); let parenthesize = match parentheses { Parentheses::Preserve => is_expression_parenthesized( expression.into(), f.context().comments().ranges(), f.context().source(), ), Parentheses::Always => true, // Fluent style means we already have parentheses Parentheses::Never => false, }; if parenthesize { let comments = f.context().comments().clone(); let node_comments = comments.leading_dangling_trailing(expression); if !node_comments.has_leading() && !node_comments.has_trailing() { parenthesized("(", &format_expr, ")") .with_hugging(is_expression_huggable(expression, f.context())) .fmt(f) } else { format_with_parentheses_comments(expression, &node_comments, f) } } else { let level = match f.context().node_level() { NodeLevel::TopLevel(_) | NodeLevel::CompoundStatement => { NodeLevel::Expression(None) } saved_level @ (NodeLevel::Expression(_) | NodeLevel::ParenthesizedExpression) => { saved_level } }; let mut f = WithNodeLevel::new(level, f); write!(f, [format_expr]) } } } /// The comments below are trailing on the addition, but it's also outside the /// parentheses /// ```python /// x = [ /// # comment leading /// (1 + 2) # comment trailing /// ] /// ``` /// as opposed to /// ```python /// x = [( /// # comment leading /// 1 + 2 # comment trailing /// )] /// ``` /// , where the comments are inside the parentheses. That is also affects list /// formatting, where we want to avoid moving the comments after the comma inside /// the parentheses: /// ```python /// data = [ /// ( /// b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" /// b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" /// ), # Point (0 0) /// ] /// ``` /// We could mark those comments as trailing in list but it's easier to handle /// them here too. /// /// So given /// ```python /// x = [ /// # comment leading outer /// ( /// # comment leading inner /// 1 + 2 # comment trailing inner /// ) # comment trailing outer /// ] /// ``` /// we want to keep the inner an outer comments outside the parentheses and the inner ones inside. /// This is independent of whether they are own line or end-of-line comments, though end-of-line /// comments can become own line comments when we discard nested parentheses. /// /// Style decision: When there are multiple nested parentheses around an expression, we consider the /// outermost parentheses the relevant ones and discard the others. fn format_with_parentheses_comments( expression: &Expr, node_comments: &LeadingDanglingTrailingComments, f: &mut PyFormatter, ) -> FormatResult<()> { // First part: Split the comments // TODO(konstin): We don't have the parent, which is a problem: // ```python // f( // # a // (a) // ) // ``` // gets formatted as // ```python // f( // ( // # a // a // ) // ) // ``` let range_with_parens = parentheses_iterator( expression.into(), None, f.context().comments().ranges(), f.context().source(), ) .last(); let (leading_split, trailing_split) = if let Some(range_with_parens) = range_with_parens { let leading_split = node_comments .leading .partition_point(|comment| comment.start() < range_with_parens.start()); let trailing_split = node_comments .trailing .partition_point(|comment| comment.start() < range_with_parens.end()); (leading_split, trailing_split) } else { (0, node_comments.trailing.len()) }; let (leading_outer, leading_inner) = node_comments.leading.split_at(leading_split); let (trailing_inner, trailing_outer) = node_comments.trailing.split_at(trailing_split); // Preserve an opening parentheses comment // ```python // a = ( # opening parentheses comment // # leading inner // 1 // ) // ``` let (parentheses_comment, leading_inner) = match leading_inner.split_first() { Some((first, rest)) if first.line_position().is_end_of_line() => { (slice::from_ref(first), rest) } _ => (Default::default(), node_comments.leading), }; // Second Part: Format // The code order is a bit strange here, we format: // * outer leading comment // * opening parenthesis // * opening parenthesis comment // * inner leading comments // * the expression itself // * inner trailing comments // * the closing parenthesis // * outer trailing comments let fmt_fields = format_with(|f| match expression { Expr::BoolOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::NamedExpr(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::BinOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::UnaryOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Lambda(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::IfExp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Dict(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Set(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::ListComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::SetComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::DictComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::GeneratorExp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Await(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Yield(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::YieldFrom(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Compare(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Call(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::FString(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::StringLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::BytesLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::NumberLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::BooleanLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::NoneLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::EllipsisLiteral(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Attribute(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Subscript(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Starred(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Name(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::List(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Tuple(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::Slice(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), Expr::IpyEscapeCommand(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f), }); leading_comments(leading_outer).fmt(f)?; // Custom FormatNodeRule::fmt variant that only formats the inner comments let format_node_rule_fmt = format_with(|f| { // No need to handle suppression comments, those are statement only leading_comments(leading_inner).fmt(f)?; let is_source_map_enabled = f.options().source_map_generation().is_enabled(); if is_source_map_enabled { source_position(expression.start()).fmt(f)?; } fmt_fields.fmt(f)?; if is_source_map_enabled { source_position(expression.end()).fmt(f)?; } trailing_comments(trailing_inner).fmt(f) }); // The actual parenthesized formatting parenthesized("(", &format_node_rule_fmt, ")") .with_dangling_comments(parentheses_comment) .fmt(f)?; trailing_comments(trailing_outer).fmt(f)?; Ok(()) } /// Wraps an expression in an optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation /// indicates that it is okay to omit the parentheses. For example, parentheses can always be omitted for lists, /// because they already bring their own parentheses. pub(crate) fn maybe_parenthesize_expression<'a, T>( expression: &'a Expr, parent: T, parenthesize: Parenthesize, ) -> MaybeParenthesizeExpression<'a> where T: Into>, { MaybeParenthesizeExpression { expression, parent: parent.into(), parenthesize, } } pub(crate) struct MaybeParenthesizeExpression<'a> { expression: &'a Expr, parent: AnyNodeRef<'a>, parenthesize: Parenthesize, } impl Format> for MaybeParenthesizeExpression<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { let MaybeParenthesizeExpression { expression, parent, parenthesize, } = self; let preserve_parentheses = parenthesize.is_optional() && is_expression_parenthesized( (*expression).into(), f.context().comments().ranges(), f.context().source(), ); // If we want to preserve parentheses, short-circuit. if preserve_parentheses { return expression.format().with_options(Parentheses::Always).fmt(f); } let comments = f.context().comments().clone(); let node_comments = comments.leading_dangling_trailing(*expression); // If the expression has comments, we always want to preserve the parentheses. This also // ensures that we correctly handle parenthesized comments, and don't need to worry about // them in the implementation below. if node_comments.has_leading() || node_comments.has_trailing_own_line() { return expression.format().with_options(Parentheses::Always).fmt(f); } let needs_parentheses = match expression.needs_parentheses(*parent, f.context()) { OptionalParentheses::Always => OptionalParentheses::Always, // The reason to add parentheses is to avoid a syntax error when breaking an expression over multiple lines. // Therefore, it is unnecessary to add an additional pair of parentheses if an outer expression // is parenthesized. _ if f.context().node_level().is_parenthesized() => OptionalParentheses::Never, needs_parentheses => needs_parentheses, }; match needs_parentheses { OptionalParentheses::Multiline => match parenthesize { Parenthesize::IfBreaksOrIfRequired => { parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) .fmt(f) } Parenthesize::IfRequired => { expression.format().with_options(Parentheses::Never).fmt(f) } Parenthesize::Optional | Parenthesize::IfBreaks => { if can_omit_optional_parentheses(expression, f.context()) { optional_parentheses(&expression.format().with_options(Parentheses::Never)) .fmt(f) } else { parenthesize_if_expands( &expression.format().with_options(Parentheses::Never), ) .fmt(f) } } }, OptionalParentheses::BestFit => match parenthesize { Parenthesize::IfBreaksOrIfRequired => { parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) .fmt(f) } Parenthesize::Optional | Parenthesize::IfRequired => { expression.format().with_options(Parentheses::Never).fmt(f) } Parenthesize::IfBreaks => { if node_comments.has_trailing() { expression.format().with_options(Parentheses::Always).fmt(f) } else { // The group id is necessary because the nested expressions may reference it. let group_id = f.group_id("optional_parentheses"); let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f); best_fit_parenthesize(&expression.format().with_options(Parentheses::Never)) .with_group_id(Some(group_id)) .fmt(f) } } }, OptionalParentheses::Never => match parenthesize { Parenthesize::IfBreaksOrIfRequired => { parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) .with_indent(is_expression_huggable(expression, f.context()).is_none()) .fmt(f) } Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfRequired => { expression.format().with_options(Parentheses::Never).fmt(f) } }, OptionalParentheses::Always => { expression.format().with_options(Parentheses::Always).fmt(f) } } } } impl NeedsParentheses for Expr { fn needs_parentheses( &self, parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { match self { Expr::BoolOp(expr) => expr.needs_parentheses(parent, context), Expr::NamedExpr(expr) => expr.needs_parentheses(parent, context), Expr::BinOp(expr) => expr.needs_parentheses(parent, context), Expr::UnaryOp(expr) => expr.needs_parentheses(parent, context), Expr::Lambda(expr) => expr.needs_parentheses(parent, context), Expr::IfExp(expr) => expr.needs_parentheses(parent, context), Expr::Dict(expr) => expr.needs_parentheses(parent, context), Expr::Set(expr) => expr.needs_parentheses(parent, context), Expr::ListComp(expr) => expr.needs_parentheses(parent, context), Expr::SetComp(expr) => expr.needs_parentheses(parent, context), Expr::DictComp(expr) => expr.needs_parentheses(parent, context), Expr::GeneratorExp(expr) => expr.needs_parentheses(parent, context), Expr::Await(expr) => expr.needs_parentheses(parent, context), Expr::Yield(expr) => expr.needs_parentheses(parent, context), Expr::YieldFrom(expr) => expr.needs_parentheses(parent, context), Expr::Compare(expr) => expr.needs_parentheses(parent, context), Expr::Call(expr) => expr.needs_parentheses(parent, context), Expr::FString(expr) => expr.needs_parentheses(parent, context), Expr::StringLiteral(expr) => expr.needs_parentheses(parent, context), Expr::BytesLiteral(expr) => expr.needs_parentheses(parent, context), Expr::NumberLiteral(expr) => expr.needs_parentheses(parent, context), Expr::BooleanLiteral(expr) => expr.needs_parentheses(parent, context), Expr::NoneLiteral(expr) => expr.needs_parentheses(parent, context), Expr::EllipsisLiteral(expr) => expr.needs_parentheses(parent, context), Expr::Attribute(expr) => expr.needs_parentheses(parent, context), Expr::Subscript(expr) => expr.needs_parentheses(parent, context), Expr::Starred(expr) => expr.needs_parentheses(parent, context), Expr::Name(expr) => expr.needs_parentheses(parent, context), Expr::List(expr) => expr.needs_parentheses(parent, context), Expr::Tuple(expr) => expr.needs_parentheses(parent, context), Expr::Slice(expr) => expr.needs_parentheses(parent, context), Expr::IpyEscapeCommand(expr) => expr.needs_parentheses(parent, context), } } } impl<'ast> AsFormat> for Expr { type Format<'a> = FormatRefWithRule<'a, Expr, FormatExpr, PyFormatContext<'ast>>; fn format(&self) -> Self::Format<'_> { FormatRefWithRule::new(self, FormatExpr::default()) } } impl<'ast> IntoFormat> for Expr { type Format = FormatOwnedWithRule>; fn into_format(self) -> Self::Format { FormatOwnedWithRule::new(self, FormatExpr::default()) } } /// Tests if it is safe to omit the optional parentheses. /// /// We prefer parentheses at least in the following cases: /// * The expression contains more than one unparenthesized expression with the same precedence. For example, /// the expression `a * b * c` contains two multiply operations. We prefer parentheses in that case. /// `(a * b) * c` or `a * b + c` are okay, because the subexpression is parenthesized, or the expression uses operands with a lower precedence /// * The expression contains at least one parenthesized sub expression (optimization to avoid unnecessary work) /// /// This mimics Black's [`_maybe_split_omitting_optional_parens`](https://github.com/psf/black/blob/d1248ca9beaf0ba526d265f4108836d89cf551b7/src/black/linegen.py#L746-L820) #[allow(clippy::if_same_then_else)] pub(crate) fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool { let mut visitor = CanOmitOptionalParenthesesVisitor::new(context); visitor.visit_subexpression(expr); if !visitor.any_parenthesized_expressions { // Only use the more complex IR when there is any expression that we can possibly split by false } else if visitor.max_precedence_count > 1 { false } else if visitor.max_precedence == OperatorPrecedence::None { // Micha: This seems to apply for lambda expressions where the body ends in a subscript. // Subscripts are excluded by default because breaking them looks odd, but it seems to be fine for lambda expression. // // ```python // mapper = lambda x: dict_with_default[ // np.nan if isinstance(x, float) and np.isnan(x) else x // ] // ``` // // to prevent that it gets formatted as: // // ```python // mapper = ( // lambda x: dict_with_default[ // np.nan if isinstance(x, float) and np.isnan(x) else x // ] // ) // ``` // I think we should remove this check in the future and instead parenthesize the body of the lambda expression: // // ```python // mapper = lambda x: ( // dict_with_default[ // np.nan if isinstance(x, float) and np.isnan(x) else x // ] // ) // ``` // // Another case are method chains: // ```python // xxxxxxxx.some_kind_of_method( // some_argument=[ // "first", // "second", // "third", // ] // ).another_method(a) // ``` true } else if visitor.max_precedence == OperatorPrecedence::Attribute { // A single method call inside a named expression (`:=`) or as the body of a lambda function: // ```python // kwargs["open_with"] = lambda path, _: fsspec.open( // path, "wb", **(storage_options or {}) // ).open() // // if ret := subprocess.run( // ["git", "rev-parse", "--short", "HEAD"], // cwd=package_dir, // capture_output=True, // encoding="ascii", // errors="surrogateescape", // ).stdout: // ``` true } else { fn is_parenthesized(expr: &Expr, context: &PyFormatContext) -> bool { // Don't break subscripts except in parenthesized context. It looks weird. !expr.is_subscript_expr() && has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty) } // Only use the layout if the first expression starts with parentheses // or the last expression ends with parentheses of some sort, and // those parentheses are non-empty. visitor .last .is_some_and(|last| is_parenthesized(last, context)) || visitor .first .expression() .is_some_and(|first| is_parenthesized(first, context)) } } #[derive(Clone, Debug)] struct CanOmitOptionalParenthesesVisitor<'input> { max_precedence: OperatorPrecedence, max_precedence_count: u32, any_parenthesized_expressions: bool, last: Option<&'input Expr>, first: First<'input>, context: &'input PyFormatContext<'input>, } impl<'input> CanOmitOptionalParenthesesVisitor<'input> { fn new(context: &'input PyFormatContext) -> Self { Self { context, max_precedence: OperatorPrecedence::None, max_precedence_count: 0, any_parenthesized_expressions: false, last: None, first: First::None, } } fn update_max_precedence(&mut self, precedence: OperatorPrecedence) { self.update_max_precedence_with_count(precedence, 1); } fn update_max_precedence_with_count(&mut self, precedence: OperatorPrecedence, count: u32) { match self.max_precedence.cmp(&precedence) { Ordering::Less => { self.max_precedence_count = count; self.max_precedence = precedence; } Ordering::Equal => { self.max_precedence_count += count; } Ordering::Greater => {} } } // Visits a subexpression, ignoring whether it is parenthesized or not fn visit_subexpression(&mut self, expr: &'input Expr) { match expr { Expr::Dict(_) | Expr::List(_) | Expr::Set(_) | Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) => { self.any_parenthesized_expressions = true; // The values are always parenthesized, don't visit. return; } Expr::Tuple(tuple) if is_tuple_parenthesized(tuple, self.context.source()) => { self.any_parenthesized_expressions = true; // The values are always parenthesized, don't visit. return; } Expr::GeneratorExp(generator) if is_generator_parenthesized(generator, self.context.source()) => { self.any_parenthesized_expressions = true; // The values are always parenthesized, don't visit. return; } // It's impossible for a file smaller or equal to 4GB to contain more than 2^32 comparisons // because each comparison requires a left operand, and `n` `operands` and right sides. #[allow(clippy::cast_possible_truncation)] Expr::BoolOp(ast::ExprBoolOp { range: _, op: _, values, }) => self.update_max_precedence_with_count( OperatorPrecedence::BooleanOperation, values.len().saturating_sub(1) as u32, ), Expr::BinOp(ast::ExprBinOp { op, left: _, right: _, range: _, }) => self.update_max_precedence(OperatorPrecedence::from(*op)), Expr::IfExp(_) => { // + 1 for the if and one for the else self.update_max_precedence_with_count(OperatorPrecedence::Conditional, 2); } // It's impossible for a file smaller or equal to 4GB to contain more than 2^32 comparisons // because each comparison requires a left operand, and `n` `operands` and right sides. #[allow(clippy::cast_possible_truncation)] Expr::Compare(ast::ExprCompare { range: _, left: _, ops, comparators: _, }) => { self.update_max_precedence_with_count( OperatorPrecedence::Comparator, ops.len() as u32, ); } Expr::Call(ast::ExprCall { range: _, func, arguments: _, }) => { self.any_parenthesized_expressions = true; // Only walk the function, the arguments are always parenthesized self.visit_expr(func); self.last = Some(expr); return; } Expr::Subscript(ast::ExprSubscript { value, .. }) => { self.any_parenthesized_expressions = true; // Only walk the function, the subscript is always parenthesized self.visit_expr(value); self.last = Some(expr); // Don't walk the slice, because the slice is always parenthesized. return; } // `[a, b].test.test[300].dot` Expr::Attribute(ast::ExprAttribute { range: _, value, attr: _, ctx: _, }) => { self.visit_expr(value); if has_parentheses(value, self.context).is_some() { self.update_max_precedence(OperatorPrecedence::Attribute); } self.last = Some(expr); return; } Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) if value.is_implicit_concatenated() => { self.update_max_precedence(OperatorPrecedence::String); } Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) if value.is_implicit_concatenated() => { self.update_max_precedence(OperatorPrecedence::String); } Expr::FString(ast::ExprFString { value, .. }) if value.is_implicit_concatenated() => { self.update_max_precedence(OperatorPrecedence::String); return; } // Non terminal nodes that don't have a termination token. Expr::NamedExpr(_) | Expr::GeneratorExp(_) | Expr::Tuple(_) => {} // Expressions with sub expressions but a preceding token // Mark this expression as first expression and not the sub expression. // Visit the sub-expressions because the sub expressions may be the end of the entire expression. Expr::UnaryOp(ast::ExprUnaryOp { range: _, op, operand: _, }) => { if op.is_invert() { self.update_max_precedence(OperatorPrecedence::BitwiseInversion); } self.first.set_if_none(First::Token); } Expr::Lambda(_) | Expr::Await(_) | Expr::Yield(_) | Expr::YieldFrom(_) | Expr::Starred(_) => { self.first.set_if_none(First::Token); } // Terminal nodes or nodes that wrap a sub-expression (where the sub expression can never be at the end). Expr::FString(_) | Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) | Expr::NoneLiteral(_) | Expr::EllipsisLiteral(_) | Expr::Name(_) | Expr::Slice(_) | Expr::IpyEscapeCommand(_) => { return; } }; walk_expr(self, expr); } } impl<'input> PreorderVisitor<'input> for CanOmitOptionalParenthesesVisitor<'input> { fn visit_expr(&mut self, expr: &'input Expr) { self.last = Some(expr); // Rule only applies for non-parenthesized expressions. if is_expression_parenthesized( expr.into(), self.context.comments().ranges(), self.context.source(), ) { self.any_parenthesized_expressions = true; } else { self.visit_subexpression(expr); } self.first.set_if_none(First::Expression(expr)); } } #[derive(Copy, Clone, Debug)] enum First<'a> { None, /// Expression starts with a non-parentheses token. E.g. `not a` Token, Expression(&'a Expr), } impl<'a> First<'a> { #[inline] fn set_if_none(&mut self, first: First<'a>) { if matches!(self, First::None) { *self = first; } } fn expression(self) -> Option<&'a Expr> { match self { First::None | First::Token => None, First::Expression(expr) => Some(expr), } } } /// A call chain consists only of attribute access (`.` operator), function/method calls and /// subscripts. We use fluent style for the call chain if there are at least two attribute dots /// after call parentheses or subscript brackets. In case of fluent style the parentheses/bracket /// will close on the previous line and the dot gets its own line, otherwise the line will start /// with the closing parentheses/bracket and the dot follows immediately after. /// /// Below, the left hand side of the addition has only a single attribute access after a call, the /// second `.filter`. The first `.filter` is a call, but it doesn't follow a call. The right hand /// side has two, the `.limit_results` after the call and the `.filter` after the subscript, so it /// gets formatted in fluent style. The outer expression we assign to `blogs` has zero since the /// `.all` follows attribute parentheses and not call parentheses. /// /// ```python /// blogs = ( /// Blog.objects.filter( /// entry__headline__contains="Lennon", /// ).filter( /// entry__pub_date__year=2008, /// ) /// + Blog.objects.filter( /// entry__headline__contains="McCartney", /// ) /// .limit_results[:10] /// .filter( /// entry__pub_date__year=2010, /// ) /// ).all() /// ``` #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum CallChainLayout { /// The root of a call chain #[default] Default, /// A nested call chain element that uses fluent style. Fluent, /// A nested call chain element not using fluent style. NonFluent, } impl CallChainLayout { pub(crate) fn from_expression( mut expr: ExpressionRef, comment_ranges: &CommentRanges, source: &str, ) -> Self { let mut attributes_after_parentheses = 0; loop { match expr { ExpressionRef::Attribute(ast::ExprAttribute { value, .. }) => { // ``` // f().g // ^^^ value // data[:100].T // ^^^^^^^^^^ value // ``` if is_expression_parenthesized(value.into(), comment_ranges, source) { // `(a).b`. We preserve these parentheses so don't recurse attributes_after_parentheses += 1; break; } else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) { attributes_after_parentheses += 1; } expr = ExpressionRef::from(value.as_ref()); } // ``` // f() // ^^^ expr // ^ func // data[:100] // ^^^^^^^^^^ expr // ^^^^ value // ``` ExpressionRef::Call(ast::ExprCall { func: inner, .. }) | ExpressionRef::Subscript(ast::ExprSubscript { value: inner, .. }) => { expr = ExpressionRef::from(inner.as_ref()); } _ => { // We to format the following in fluent style: // ``` // f2 = (a).w().t(1,) // ^ expr // ``` if is_expression_parenthesized(expr, comment_ranges, source) { attributes_after_parentheses += 1; } break; } } // We preserve these parentheses so don't recurse if is_expression_parenthesized(expr, comment_ranges, source) { break; } } if attributes_after_parentheses < 2 { CallChainLayout::NonFluent } else { CallChainLayout::Fluent } } /// Determine whether to actually apply fluent layout in attribute, call and subscript /// formatting pub(crate) fn apply_in_node<'a>( self, item: impl Into>, f: &mut PyFormatter, ) -> CallChainLayout { match self { CallChainLayout::Default => { if f.context().node_level().is_parenthesized() { CallChainLayout::from_expression( item.into(), f.context().comments().ranges(), f.context().source(), ) } else { CallChainLayout::NonFluent } } layout @ (CallChainLayout::Fluent | CallChainLayout::NonFluent) => layout, } } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum OwnParentheses { /// The node has parentheses, but they are empty (e.g., `[]` or `f()`). Empty, /// The node has parentheses, and they are non-empty (e.g., `[1]` or `f(1)`). NonEmpty, } impl OwnParentheses { const fn is_non_empty(self) -> bool { matches!(self, OwnParentheses::NonEmpty) } } /// Returns the [`OwnParentheses`] value for a given [`Expr`], to indicate whether it has its /// own parentheses or is itself parenthesized. /// /// Differs from [`has_own_parentheses`] in that it returns [`OwnParentheses::NonEmpty`] for /// parenthesized expressions, like `(1)` or `([1])`, regardless of whether those expression have /// their _own_ parentheses. pub(crate) fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option { let own_parentheses = has_own_parentheses(expr, context); // If the node has its own non-empty parentheses, we don't need to check for surrounding // parentheses (e.g., `[1]`, or `([1])`). if own_parentheses == Some(OwnParentheses::NonEmpty) { return own_parentheses; } // Otherwise, if the node lacks parentheses (e.g., `(1)`) or only contains empty parentheses // (e.g., `([])`), we need to check for surrounding parentheses. if is_expression_parenthesized(expr.into(), context.comments().ranges(), context.source()) { return Some(OwnParentheses::NonEmpty); } own_parentheses } /// Returns the [`OwnParentheses`] value for a given [`Expr`], to indicate whether it has its /// own parentheses, and whether those parentheses are empty. /// /// A node is considered to have its own parentheses if it includes a `[]`, `()`, or `{}` pair /// that is inherent to the node (e.g., as in `f()`, `[]`, or `{1: 2}`, but not `(a.b.c)`). /// /// Parentheses are considered to be non-empty if they contain any elements or comments. pub(crate) fn has_own_parentheses( expr: &Expr, context: &PyFormatContext, ) -> Option { match expr { // These expressions are always non-empty. Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::Subscript(_) => { Some(OwnParentheses::NonEmpty) } Expr::GeneratorExp(generator) if is_generator_parenthesized(generator, context.source()) => { Some(OwnParentheses::NonEmpty) } // These expressions must contain _some_ child or trivia token in order to be non-empty. Expr::List(ast::ExprList { elts, .. }) | Expr::Set(ast::ExprSet { elts, .. }) => { if !elts.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else { Some(OwnParentheses::Empty) } } Expr::Tuple(tuple) if is_tuple_parenthesized(tuple, context.source()) => { if !tuple.elts.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else { Some(OwnParentheses::Empty) } } Expr::Dict(ast::ExprDict { keys, .. }) => { if !keys.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else { Some(OwnParentheses::Empty) } } Expr::Call(ast::ExprCall { arguments, .. }) => { if !arguments.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) { Some(OwnParentheses::NonEmpty) } else { Some(OwnParentheses::Empty) } } _ => None, } } /// Returns `true` if the expression can hug directly to enclosing parentheses, as in Black's /// `hug_parens_with_braces_and_square_brackets` or `multiline_string_handling` preview styles behavior. /// /// For example, in preview style, given: /// ```python /// ([1, 2, 3,]) /// ``` /// /// We want to format it as: /// ```python /// ([ /// 1, /// 2, /// 3, /// ]) /// ``` /// /// As opposed to: /// ```python /// ( /// [ /// 1, /// 2, /// 3, /// ] /// ) /// ``` pub(crate) fn is_expression_huggable( expr: &Expr, context: &PyFormatContext, ) -> Option { match expr { Expr::Tuple(_) | Expr::List(_) | Expr::Set(_) | Expr::Dict(_) | Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) => is_hug_parens_with_braces_and_square_brackets_enabled(context) .then_some(HuggingStyle::Always), Expr::Starred(ast::ExprStarred { value, .. }) => is_expression_huggable(value, context), Expr::StringLiteral(string) => is_huggable_string(AnyString::String(string), context), Expr::BytesLiteral(bytes) => is_huggable_string(AnyString::Bytes(bytes), context), Expr::FString(fstring) => is_huggable_string(AnyString::FString(fstring), context), Expr::BoolOp(_) | Expr::NamedExpr(_) | Expr::BinOp(_) | Expr::UnaryOp(_) | Expr::Lambda(_) | Expr::IfExp(_) | Expr::GeneratorExp(_) | Expr::Await(_) | Expr::Yield(_) | Expr::YieldFrom(_) | Expr::Compare(_) | Expr::Call(_) | Expr::Attribute(_) | Expr::Subscript(_) | Expr::Name(_) | Expr::Slice(_) | Expr::IpyEscapeCommand(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) | Expr::NoneLiteral(_) | Expr::EllipsisLiteral(_) => None, } } /// Returns `true` if `string` is a multiline string that is not implicitly concatenated. fn is_huggable_string(string: AnyString, context: &PyFormatContext) -> Option { if !is_multiline_string_handling_enabled(context) { return None; } if !string.is_implicit_concatenated() && string.is_multiline(context.source()) { Some(HuggingStyle::IfFirstLineFits) } else { None } } /// The precedence of [python operators](https://docs.python.org/3/reference/expressions.html#operator-precedence) from /// highest to lowest priority. /// /// Ruff uses the operator precedence to decide in which order to split operators: /// Operators with a lower precedence split before higher-precedence operators. /// Splitting by precedence ensures that the visual grouping reflects the precedence. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] enum OperatorPrecedence { None, Attribute, Exponential, BitwiseInversion, Multiplicative, Additive, Shift, BitwiseAnd, BitwiseXor, BitwiseOr, Comparator, // Implicit string concatenation String, BooleanOperation, Conditional, } impl From for OperatorPrecedence { fn from(value: Operator) -> Self { match value { Operator::Add | Operator::Sub => OperatorPrecedence::Additive, Operator::Mult | Operator::MatMult | Operator::Div | Operator::Mod | Operator::FloorDiv => OperatorPrecedence::Multiplicative, Operator::Pow => OperatorPrecedence::Exponential, Operator::LShift | Operator::RShift => OperatorPrecedence::Shift, Operator::BitOr => OperatorPrecedence::BitwiseOr, Operator::BitXor => OperatorPrecedence::BitwiseXor, Operator::BitAnd => OperatorPrecedence::BitwiseAnd, } } } /// Returns `true` if `expr` is an expression that can be split into multiple lines. /// /// Returns `false` for expressions that are guaranteed to never split. pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) -> bool { match expr { // Single token expressions. They never have any split points. Expr::NamedExpr(_) | Expr::Name(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) | Expr::NoneLiteral(_) | Expr::EllipsisLiteral(_) | Expr::Slice(_) | Expr::IpyEscapeCommand(_) => false, // Expressions that insert split points when parenthesized. Expr::Compare(_) | Expr::BinOp(_) | Expr::BoolOp(_) | Expr::IfExp(_) | Expr::GeneratorExp(_) | Expr::Subscript(_) | Expr::Await(_) | Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::YieldFrom(_) => true, // Sequence types can split if they contain at least one element. Expr::Tuple(tuple) => !tuple.elts.is_empty(), Expr::Dict(dict) => !dict.values.is_empty(), Expr::Set(set) => !set.elts.is_empty(), Expr::List(list) => !list.elts.is_empty(), Expr::UnaryOp(unary) => is_splittable_expression(unary.operand.as_ref(), context), Expr::Yield(ast::ExprYield { value, .. }) => value.is_some(), Expr::Call(ast::ExprCall { arguments, func, .. }) => { !arguments.is_empty() || is_expression_parenthesized( func.as_ref().into(), context.comments().ranges(), context.source(), ) } // String like literals can expand if they are implicit concatenated. Expr::FString(fstring) => fstring.value.is_implicit_concatenated(), Expr::StringLiteral(string) => string.value.is_implicit_concatenated(), Expr::BytesLiteral(bytes) => bytes.value.is_implicit_concatenated(), // Expressions that have no split points per se, but they contain nested sub expressions that might expand. Expr::Lambda(ast::ExprLambda { body: expression, .. }) | Expr::Starred(ast::ExprStarred { value: expression, .. }) | Expr::Attribute(ast::ExprAttribute { value: expression, .. }) => { is_expression_parenthesized( expression.into(), context.comments().ranges(), context.source(), ) || is_splittable_expression(expression.as_ref(), context) } } }