use ruff_formatter::{FormatError, RemoveSoftLinesBuffer, format_args, write};
use ruff_python_ast::{
AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, Operator, StmtAssign, StringLike, TString,
TypeParams,
};
use crate::builders::parenthesize_if_expands;
use crate::comments::{
Comments, LeadingDanglingTrailingComments, SourceComment, trailing_comments,
};
use crate::context::{NodeLevel, WithNodeLevel};
use crate::expression::expr_lambda::ExprLambdaLayout;
use crate::expression::parentheses::{
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, optional_parentheses,
};
use crate::expression::{
can_omit_optional_parentheses, has_own_parentheses, has_parentheses,
maybe_parenthesize_expression,
};
use crate::other::interpolated_string::InterpolatedStringLayout;
use crate::prelude::*;
use crate::statement::trailing_semicolon;
use crate::string::StringLikeExtensions;
use crate::string::implicit::{
FormatImplicitConcatenatedStringExpanded, FormatImplicitConcatenatedStringFlat,
ImplicitConcatenatedLayout,
};
#[derive(Default)]
pub struct FormatStmtAssign;
impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
fn fmt_fields(&self, item: &StmtAssign, f: &mut PyFormatter) -> FormatResult<()> {
let StmtAssign {
range: _,
node_index: _,
targets,
value,
} = item;
let (first, rest) = targets.split_first().ok_or(FormatError::syntax_error(
"Expected at least on assignment target",
))?;
let format_first = FormatTargetWithEqualOperator {
target: first,
preserve_parentheses: true,
};
if let Some((last, head)) = rest.split_last() {
format_first.fmt(f)?;
for target in head {
FormatTargetWithEqualOperator {
target,
preserve_parentheses: false,
}
.fmt(f)?;
}
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(last),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f)?;
}
else if has_target_own_parentheses(first, f.context())
&& !f.context().is_expression_parenthesized(first.into())
{
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(first),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f)?;
}
else {
format_first.fmt(f)?;
FormatStatementsLastExpression::left_to_right(value, item).fmt(f)?;
}
if f.options().source_type().is_ipynb()
&& f.context().node_level().is_last_top_level_statement()
&& trailing_semicolon(item.into(), f.context().source()).is_some()
&& matches!(targets.as_slice(), [Expr::Name(_)])
{
token(";").fmt(f)?;
}
Ok(())
}
}
struct FormatTargetWithEqualOperator<'a> {
target: &'a Expr,
preserve_parentheses: bool,
}
impl Format<PyFormatContext<'_>> for FormatTargetWithEqualOperator<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
if self.preserve_parentheses
|| f.context().comments().has_leading(self.target)
|| f.context().comments().has_trailing(self.target)
{
self.target.format().fmt(f)?;
} else if should_parenthesize_target(self.target, f.context()) {
parenthesize_if_expands(&self.target.format().with_options(Parentheses::Never))
.fmt(f)?;
} else {
self.target
.format()
.with_options(Parentheses::Never)
.fmt(f)?;
}
write!(f, [space(), token("="), space()])
}
}
#[derive(Debug)]
pub(super) enum FormatStatementsLastExpression<'a> {
LeftToRight {
value: &'a Expr,
statement: AnyNodeRef<'a>,
},
RightToLeft {
before_operator: AnyBeforeOperator<'a>,
operator: AnyAssignmentOperator,
value: &'a Expr,
statement: AnyNodeRef<'a>,
},
}
impl<'a> FormatStatementsLastExpression<'a> {
pub(super) fn left_to_right<S: Into<AnyNodeRef<'a>>>(value: &'a Expr, statement: S) -> Self {
Self::LeftToRight {
value,
statement: statement.into(),
}
}
}
impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self {
FormatStatementsLastExpression::LeftToRight { value, statement } => {
let can_inline_comment = should_inline_comments(value, *statement, f.context());
let string_like = StringLike::try_from(*value).ok();
let format_interpolated_string = string_like
.and_then(|string| format_interpolated_string_assignment(string, f.context()));
let format_implicit_flat = string_like.and_then(|string| {
FormatImplicitConcatenatedStringFlat::new(string, f.context())
});
if !can_inline_comment
&& format_implicit_flat.is_none()
&& format_interpolated_string.is_none()
{
return maybe_parenthesize_value(value, *statement).fmt(f);
}
let comments = f.context().comments().clone();
let expression_comments = comments.leading_dangling_trailing(*value);
if let Some(inline_comments) = OptionalParenthesesInlinedComments::new(
&expression_comments,
*statement,
&comments,
) {
let group_id = f.group_id("optional_parentheses");
if let Some(flat) = format_implicit_flat {
inline_comments.mark_formatted();
let string = flat.string();
let flat = format_with(|f| {
if string.is_interpolated_string() {
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [flat])
} else {
flat.fmt(f)
}
})
.memoized();
if string.is_interpolated_string() && flat.inspect(f)?.will_break() {
inline_comments.mark_unformatted();
return write!(
f,
[maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks,
)]
);
}
let expanded = format_with(|f| {
let f =
&mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
write!(
f,
[FormatImplicitConcatenatedStringExpanded::new(
string,
ImplicitConcatenatedLayout::MaybeFlat
)]
)
});
let single_line = format_with(|f| write!(f, [flat, inline_comments]));
let joined_parenthesized = format_with(|f| {
group(&format_args![
token("("),
soft_block_indent(&format_args![flat, inline_comments]),
token(")"),
])
.with_id(Some(group_id))
.should_expand(true)
.fmt(f)
});
let implicit_expanded = format_with(|f| {
group(&format_args![
token("("),
block_indent(&expanded),
token(")"),
inline_comments,
])
.with_id(Some(group_id))
.should_expand(true)
.fmt(f)
});
best_fitting![single_line, joined_parenthesized, implicit_expanded]
.with_mode(BestFittingMode::AllLines)
.fmt(f)?;
} else if let Some(format_interpolated_string) = format_interpolated_string {
inline_comments.mark_formatted();
let interpolated_string_flat = format_with(|f| {
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [format_interpolated_string])
})
.memoized();
if interpolated_string_flat.inspect(f)?.will_break() {
inline_comments.mark_unformatted();
return write!(
f,
[maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks,
)]
);
}
let single_line =
format_with(|f| write!(f, [interpolated_string_flat, inline_comments]));
let joined_parenthesized = format_with(|f| {
group(&format_args![
token("("),
soft_block_indent(&format_args![
interpolated_string_flat,
inline_comments
]),
token(")"),
])
.with_id(Some(group_id))
.should_expand(true)
.fmt(f)
});
let format_interpolated_string = format_with(|f| {
write!(f, [format_interpolated_string, inline_comments])
});
best_fitting![
single_line,
joined_parenthesized,
format_interpolated_string
]
.with_mode(BestFittingMode::AllLines)
.fmt(f)?;
} else {
best_fit_parenthesize(&format_once(|f| {
inline_comments.mark_formatted();
value.format().with_options(Parentheses::Never).fmt(f)?;
if !inline_comments.is_empty() {
if_group_breaks(&inline_comments).fmt(f)?;
}
Ok(())
}))
.with_group_id(Some(group_id))
.fmt(f)?;
if !inline_comments.is_empty() {
if_group_fits_on_line(&inline_comments)
.with_group_id(Some(group_id))
.fmt(f)?;
}
}
Ok(())
} else {
value.format().with_options(Parentheses::Always).fmt(f)
}
}
FormatStatementsLastExpression::RightToLeft {
before_operator,
operator,
value,
statement,
} => {
let should_inline_comments = should_inline_comments(value, *statement, f.context());
let string_like = StringLike::try_from(*value).ok();
let format_interpolated_string = string_like
.and_then(|string| format_interpolated_string_assignment(string, f.context()));
let format_implicit_flat = string_like.and_then(|string| {
FormatImplicitConcatenatedStringFlat::new(string, f.context())
});
if !should_inline_comments
&& !should_non_inlineable_use_best_fit(value, *statement, f.context())
&& format_implicit_flat.is_none()
&& format_interpolated_string.is_none()
{
return write!(
f,
[
before_operator,
space(),
operator,
space(),
maybe_parenthesize_value(value, *statement)
]
);
}
let comments = f.context().comments().clone();
let expression_comments = comments.leading_dangling_trailing(*value);
let inline_comments = if should_inline_comments
|| format_implicit_flat.is_some()
|| format_interpolated_string.is_some()
{
OptionalParenthesesInlinedComments::new(
&expression_comments,
*statement,
&comments,
)
} else if expression_comments.has_leading()
|| expression_comments.has_trailing_own_line()
{
None
} else {
Some(OptionalParenthesesInlinedComments::default())
};
let Some(inline_comments) = inline_comments else {
return write!(
f,
[
before_operator,
space(),
operator,
space(),
value.format().with_options(Parentheses::Always)
]
);
};
inline_comments.mark_formatted();
let last_target = before_operator.memoized();
let last_target_breaks = last_target.inspect(f)?.will_break();
if format_implicit_flat.is_none()
&& format_interpolated_string.is_none()
&& last_target_breaks
{
return write!(
f,
[
last_target,
space(),
operator,
space(),
value.format().with_options(Parentheses::Never),
inline_comments
]
);
}
let format_value = format_with(|f| {
if let Some(format_implicit_flat) = format_implicit_flat.as_ref() {
if format_implicit_flat.string().is_interpolated_string() {
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [format_implicit_flat])
} else {
format_implicit_flat.fmt(f)
}
} else if let Some(format_interpolated_string) =
format_interpolated_string.as_ref()
{
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [format_interpolated_string])
} else {
value.format().with_options(Parentheses::Never).fmt(f)
}
})
.memoized();
let single_line = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
format_value,
inline_comments
]
)
});
let flat_target_parenthesize_value = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_args![
format_value,
inline_comments
]))
.should_expand(true),
token(")")
]
)
});
let split_target_flat_value = format_with(|f| {
write!(
f,
[
group(&last_target).should_expand(true),
space(),
operator,
space(),
format_value,
inline_comments
]
)
});
if value.is_call_expr() || value.is_subscript_expr() || value.is_attribute_expr() {
best_fitting![
single_line,
format_args![
last_target,
space(),
operator,
space(),
group(&format_value).should_expand(true),
],
flat_target_parenthesize_value,
split_target_flat_value
]
.fmt(f)
} else if let Some(format_implicit_flat) = &format_implicit_flat {
if format_implicit_flat.string().is_interpolated_string()
&& format_value.inspect(f)?.will_break()
{
inline_comments.mark_unformatted();
return write!(
f,
[
before_operator,
space(),
operator,
space(),
maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks
)
]
);
}
let group_id = f.group_id("optional_parentheses");
let format_expanded = format_with(|f| {
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
FormatImplicitConcatenatedStringExpanded::new(
StringLike::try_from(*value).unwrap(),
ImplicitConcatenatedLayout::MaybeFlat,
)
.fmt(f)
})
.memoized();
let flat_target_value_parenthesized_multiline = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_expanded))
.with_id(Some(group_id))
.should_expand(true),
token(")"),
inline_comments
]
)
});
let split_target_value_parenthesized_flat = format_with(|f| {
write!(
f,
[
group(&last_target).should_expand(true),
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_args![
format_value,
inline_comments
]))
.should_expand(true),
token(")")
]
)
});
let split_target_value_parenthesized_multiline = format_with(|f| {
write!(
f,
[
group(&last_target).should_expand(true),
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_expanded))
.with_id(Some(group_id))
.should_expand(true),
token(")"),
inline_comments
]
)
});
if last_target_breaks {
best_fitting![
split_target_flat_value,
split_target_value_parenthesized_flat,
split_target_value_parenthesized_multiline,
]
.with_mode(BestFittingMode::AllLines)
.fmt(f)
} else {
best_fitting![
single_line,
flat_target_parenthesize_value,
flat_target_value_parenthesized_multiline,
split_target_flat_value,
split_target_value_parenthesized_flat,
split_target_value_parenthesized_multiline,
]
.with_mode(BestFittingMode::AllLines)
.fmt(f)
}
} else if let Some(format_interpolated_string) = &format_interpolated_string {
if format_value.inspect(f)?.will_break() {
inline_comments.mark_unformatted();
return write!(
f,
[
before_operator,
space(),
operator,
space(),
maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks
)
]
);
}
let format_interpolated_string =
format_with(|f| write!(f, [format_interpolated_string, inline_comments]))
.memoized();
let flat_target_regular_interpolated_string = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
format_interpolated_string
]
)
});
let split_target_value_parenthesized_flat = format_with(|f| {
write!(
f,
[
group(&last_target).should_expand(true),
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_args![
format_value,
inline_comments
]))
.should_expand(true),
token(")")
]
)
});
let split_target_regular_interpolated_string = format_with(|f| {
write!(
f,
[
group(&last_target).should_expand(true),
space(),
operator,
space(),
format_interpolated_string,
]
)
});
if last_target_breaks {
best_fitting![
split_target_flat_value,
split_target_value_parenthesized_flat,
split_target_regular_interpolated_string,
]
.with_mode(BestFittingMode::AllLines)
.fmt(f)
} else {
best_fitting![
single_line,
flat_target_parenthesize_value,
flat_target_regular_interpolated_string,
split_target_flat_value,
split_target_value_parenthesized_flat,
split_target_regular_interpolated_string,
]
.with_mode(BestFittingMode::AllLines)
.fmt(f)
}
} else {
best_fitting![
single_line,
flat_target_parenthesize_value,
split_target_flat_value
]
.fmt(f)
}
}
}
}
}
#[derive(Debug, Copy, Clone)]
enum InterpolatedString<'a> {
FString(&'a FString),
TString(&'a TString),
}
impl Format<PyFormatContext<'_>> for InterpolatedString<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self {
InterpolatedString::FString(string) => string.format().fmt(f),
InterpolatedString::TString(string) => string.format().fmt(f),
}
}
}
fn format_interpolated_string_assignment<'a>(
string: StringLike<'a>,
context: &PyFormatContext,
) -> Option<InterpolatedString<'a>> {
let (interpolated_string, elements) = match string {
StringLike::TString(expr) => {
let t_string = expr.as_single_part_tstring()?;
(InterpolatedString::TString(t_string), &t_string.elements)
}
StringLike::FString(expr) => {
let f_string = expr.as_single_part_fstring()?;
(InterpolatedString::FString(f_string), &f_string.elements)
}
_ => {
return None;
}
};
if InterpolatedStringLayout::from_interpolated_string_elements(elements, context.source())
.is_flat()
{
return None;
}
if string.is_multiline(context) {
return None;
}
Some(interpolated_string)
}
#[derive(Debug, Default)]
struct OptionalParenthesesInlinedComments<'a> {
expression: &'a [SourceComment],
statement: &'a [SourceComment],
}
impl<'a> OptionalParenthesesInlinedComments<'a> {
fn new(
expression_comments: &LeadingDanglingTrailingComments<'a>,
statement: AnyNodeRef<'a>,
comments: &'a Comments<'a>,
) -> Option<Self> {
if expression_comments.has_leading() || expression_comments.has_trailing_own_line() {
return None;
}
let statement_trailing_comments = comments.trailing(statement);
let after_end_of_line = statement_trailing_comments
.partition_point(|comment| comment.line_position().is_end_of_line());
let (stmt_inline_comments, _) = statement_trailing_comments.split_at(after_end_of_line);
let after_end_of_line = expression_comments
.trailing
.partition_point(|comment| comment.line_position().is_end_of_line());
let (expression_inline_comments, trailing_own_line_comments) =
expression_comments.trailing.split_at(after_end_of_line);
debug_assert!(
trailing_own_line_comments.is_empty(),
"The method should have returned early if the expression has trailing own line comments"
);
Some(OptionalParenthesesInlinedComments {
expression: expression_inline_comments,
statement: stmt_inline_comments,
})
}
fn is_empty(&self) -> bool {
self.expression.is_empty() && self.statement.is_empty()
}
fn iter_comments(&self) -> impl Iterator<Item = &'a SourceComment> {
self.expression.iter().chain(self.statement)
}
fn mark_formatted(&self) {
for comment in self.expression {
comment.mark_formatted();
}
}
fn mark_unformatted(&self) {
for comment in self.expression {
comment.mark_unformatted();
}
}
}
impl Format<PyFormatContext<'_>> for OptionalParenthesesInlinedComments<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
for comment in self.iter_comments() {
comment.mark_unformatted();
}
write!(
f,
[
trailing_comments(self.expression),
trailing_comments(self.statement)
]
)
}
}
#[derive(Copy, Clone, Debug)]
pub(super) enum AnyAssignmentOperator {
Assign,
AugAssign(Operator),
}
impl Format<PyFormatContext<'_>> for AnyAssignmentOperator {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self {
AnyAssignmentOperator::Assign => token("=").fmt(f),
AnyAssignmentOperator::AugAssign(operator) => {
write!(f, [operator.format(), token("=")])
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub(super) enum AnyBeforeOperator<'a> {
Expression(&'a Expr),
TypeParams(&'a TypeParams),
}
impl Format<PyFormatContext<'_>> for AnyBeforeOperator<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self {
AnyBeforeOperator::Expression(expression) => {
if f.context().comments().has_leading(*expression)
|| f.context().comments().has_trailing(*expression)
{
expression
.format()
.with_options(Parentheses::Preserve)
.fmt(f)
}
else if should_parenthesize_target(expression, f.context()) {
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)
}
} else {
expression.format().with_options(Parentheses::Never).fmt(f)
}
}
AnyBeforeOperator::TypeParams(type_params) => type_params.format().fmt(f),
}
}
}
fn should_inline_comments(
expression: &Expr,
parent: AnyNodeRef,
context: &PyFormatContext,
) -> bool {
match expression {
Expr::Name(_) | Expr::NoneLiteral(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) => {
true
}
Expr::StringLiteral(string) => {
string.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::BytesLiteral(bytes) => {
bytes.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::FString(fstring) => {
fstring.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::TString(tstring) => {
tstring.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
_ => false,
}
}
fn should_non_inlineable_use_best_fit(
expr: &Expr,
parent: AnyNodeRef,
context: &PyFormatContext,
) -> bool {
match expr {
Expr::Attribute(attribute) => {
attribute.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::Call(call) => call.needs_parentheses(parent, context) == OptionalParentheses::BestFit,
Expr::Subscript(subscript) => {
subscript.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
_ => false,
}
}
pub(super) fn has_target_own_parentheses(target: &Expr, context: &PyFormatContext) -> bool {
matches!(target, Expr::Tuple(_)) || has_own_parentheses(target, context).is_some()
}
pub(super) fn should_parenthesize_target(target: &Expr, context: &PyFormatContext) -> bool {
!(has_target_own_parentheses(target, context)
|| is_attribute_with_parenthesized_value(target, context))
}
fn is_attribute_with_parenthesized_value(target: &Expr, context: &PyFormatContext) -> bool {
match target {
Expr::Attribute(ExprAttribute { value, .. }) => {
has_parentheses(value.as_ref(), context).is_some()
|| is_attribute_with_parenthesized_value(value, context)
}
Expr::Subscript(_) => true,
Expr::Call(ExprCall { arguments, .. }) => !arguments.is_empty(),
_ => false,
}
}
fn maybe_parenthesize_value<'a>(
expression: &'a Expr,
parent: AnyNodeRef<'a>,
) -> MaybeParenthesizeValue<'a> {
MaybeParenthesizeValue { expression, parent }
}
struct MaybeParenthesizeValue<'a> {
expression: &'a Expr,
parent: AnyNodeRef<'a>,
}
impl Format<PyFormatContext<'_>> for MaybeParenthesizeValue<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let MaybeParenthesizeValue { expression, parent } = self;
if let Expr::Lambda(lambda) = expression
&& !f.context().comments().has_leading(lambda)
{
parenthesize_if_expands(&lambda.format().with_options(ExprLambdaLayout::Assignment))
.fmt(f)
} else {
maybe_parenthesize_expression(expression, *parent, Parenthesize::IfBreaks).fmt(f)
}
}
}