From c0565b97478ba82740fa1a97704b56d8370d9c74 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 15 Oct 2024 18:23:00 +0200 Subject: [PATCH] More in progress work --- crates/ruff/tests/.format.rs.pending-snap | 37 ------ crates/ruff_formatter/src/builders.rs | 2 +- .../src/expression/expr_bytes_literal.rs | 29 ++++- .../src/expression/expr_f_string.rs | 37 +++++- .../src/expression/expr_string_literal.rs | 46 ++++++-- .../src/expression/mod.rs | 11 +- .../src/other/arguments.rs | 2 + .../ruff_python_formatter/src/pattern/mod.rs | 16 ++- .../src/statement/stmt_assign.rs | 108 +++++++++++++++--- .../src/statement/suite.rs | 3 +- .../ruff_python_formatter/src/string/any.rs | 16 +++ .../ruff_python_formatter/src/string/mod.rs | 2 +- ...ack_compatibility@cases__fmtonoff5.py.snap | 4 +- ...expression__binary_implicit_string.py.snap | 7 +- .../format@expression__bytes.py.snap | 7 +- .../format@expression__fstring.py.snap | 8 +- .../format@expression__string.py.snap | 7 +- .../format@expression__yield.py.snap | 23 +--- ...attern__pattern_maybe_parenthesize.py.snap | 52 ++++++++- ...atement__return_type_no_parameters.py.snap | 4 +- ...@statement__return_type_parameters.py.snap | 4 +- 21 files changed, 295 insertions(+), 130 deletions(-) delete mode 100644 crates/ruff/tests/.format.rs.pending-snap diff --git a/crates/ruff/tests/.format.rs.pending-snap b/crates/ruff/tests/.format.rs.pending-snap deleted file mode 100644 index 851d7c66e94783..00000000000000 --- a/crates/ruff/tests/.format.rs.pending-snap +++ /dev/null @@ -1,37 +0,0 @@ -{"run_id":"1728479378-856636000","line":21,"new":{"module_name":"format","snapshot_name":"default_options","metadata":{"source":"crates/ruff/tests/format.rs","assertion_line":21,"info":{"program":"ruff","args":["format","--isolated","--stdin-filename","test.py","-"],"stdin":"\ndef foo(arg1, arg2,):\n print('Shouldn\\'t change quotes')\n\n\nif condition:\n\n print('Hy \"Micha\"') # Should not change quotes\n\n"}},"snapshot":"success: true\nexit_code: 0\n----- stdout -----\ndef foo(\n arg1,\n arg2,\n):\n print(\"'t change quotes\")\n\n\nif condition:\n print('Hy \"Micha\"') # Should not change quotes\n\n----- stderr -----\n"},"old":{"module_name":"format","metadata":{},"snapshot":"success: true\nexit_code: 0\n----- stdout -----\ndef foo(\n arg1,\n arg2,\n):\n print(\"Shouldn't change quotes\")\n\n\nif condition:\n print('Hy \"Micha\"') # Should not change quotes\n\n----- stderr -----"}} -{"run_id":"1728479378-856636000","line":668,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":336,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":67,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":486,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1747,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":708,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":760,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":634,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":100,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":84,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":842,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":292,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1941,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1766,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":546,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":424,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1851,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1833,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":2033,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1791,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1873,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1965,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1895,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1920,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1254,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":559,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":517,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":158,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1230,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1133,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1196,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1987,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1269,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":571,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1026,"new":null,"old":null} -{"run_id":"1728479378-856636000","line":1068,"new":null,"old":null} diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 33ea49724eb24b..21ab988b5e38e0 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -1454,7 +1454,7 @@ impl std::fmt::Debug for Group<'_, Context> { /// layout doesn't exceed the line width too, in which case it falls back to the flat layout. /// /// This IR is identical to the following [`best_fitting`] layout but is implemented as custom IR for -/// best performance. +/// better performance. /// /// ```rust /// # use ruff_formatter::prelude::*; diff --git a/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs b/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs index 132f08b9d66bd0..f2aceee0b1c039 100644 --- a/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_bytes_literal.rs @@ -1,3 +1,5 @@ +use ruff_formatter::FormatRuleWithOptions; +use ruff_formatter::GroupId; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprBytesLiteral; @@ -8,7 +10,23 @@ use crate::prelude::*; use crate::string::{AnyString, FormatImplicitConcatenatedString}; #[derive(Default)] -pub struct FormatExprBytesLiteral; +pub struct FormatExprBytesLiteral { + layout: ExprBytesLiteralLayout, +} + +#[derive(Default)] +pub struct ExprBytesLiteralLayout { + pub implicit_group_id: Option, +} + +impl FormatRuleWithOptions> for FormatExprBytesLiteral { + type Options = ExprBytesLiteralLayout; + + fn with_options(mut self, options: Self::Options) -> Self { + self.layout = options; + self + } +} impl FormatNodeRule for FormatExprBytesLiteral { fn fmt_fields(&self, item: &ExprBytesLiteral, f: &mut PyFormatter) -> FormatResult<()> { @@ -16,7 +34,14 @@ impl FormatNodeRule for FormatExprBytesLiteral { match value.as_slice() { [bytes_literal] => bytes_literal.format().fmt(f), - _ => in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f), + _ => match self.layout.implicit_group_id { + Some(group_id) => group(&FormatImplicitConcatenatedString::new(item)) + .with_group_id(Some(group_id)) + .fmt(f), + None => { + in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f) + } + }, } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_f_string.rs b/crates/ruff_python_formatter/src/expression/expr_f_string.rs index 4fe2d48469b9be..db3eff2aa2fe21 100644 --- a/crates/ruff_python_formatter/src/expression/expr_f_string.rs +++ b/crates/ruff_python_formatter/src/expression/expr_f_string.rs @@ -1,3 +1,4 @@ +use ruff_formatter::{FormatRuleWithOptions, GroupId}; use ruff_python_ast::{AnyNodeRef, ExprFString}; use ruff_source_file::Locator; use ruff_text_size::Ranged; @@ -10,7 +11,23 @@ use crate::prelude::*; use crate::string::{AnyString, FormatImplicitConcatenatedString, Quoting}; #[derive(Default)] -pub struct FormatExprFString; +pub struct FormatExprFString { + layout: ExprFStringLayout, +} + +#[derive(Default)] +pub struct ExprFStringLayout { + pub implicit_group_id: Option, +} + +impl FormatRuleWithOptions> for FormatExprFString { + type Options = ExprFStringLayout; + + fn with_options(mut self, options: Self::Options) -> Self { + self.layout = options; + self + } +} impl FormatNodeRule for FormatExprFString { fn fmt_fields(&self, item: &ExprFString, f: &mut PyFormatter) -> FormatResult<()> { @@ -21,7 +38,14 @@ impl FormatNodeRule for FormatExprFString { FormatFStringPart::new(f_string_part, f_string_quoting(item, f.context().locator())) .fmt(f) } - _ => in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f), + _ => match self.layout.implicit_group_id { + Some(group_id) => group(&FormatImplicitConcatenatedString::new(item)) + .with_group_id(Some(group_id)) + .fmt(f), + None => { + in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f) + } + }, } } } @@ -32,8 +56,9 @@ impl NeedsParentheses for ExprFString { _parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { - if self.value.is_implicit_concatenated() { - OptionalParentheses::Multiline + // if self.value.is_implicit_concatenated() { + // OptionalParentheses::Multiline + // } else // TODO(dhruvmanila): Ideally what we want here is a new variant which // is something like: // - If the expression fits by just adding the parentheses, then add them and @@ -52,7 +77,9 @@ impl NeedsParentheses for ExprFString { // ``` // This isn't decided yet, refer to the relevant discussion: // https://github.com/astral-sh/ruff/discussions/9785 - } else if AnyString::FString(self).is_multiline(context.source()) { + if !self.value.is_implicit_concatenated() + && AnyString::FString(self).is_multiline(context.source()) + { OptionalParentheses::Never } else { OptionalParentheses::BestFit diff --git a/crates/ruff_python_formatter/src/expression/expr_string_literal.rs b/crates/ruff_python_formatter/src/expression/expr_string_literal.rs index 3c202435d1ea82..5b9d8994e1b002 100644 --- a/crates/ruff_python_formatter/src/expression/expr_string_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_string_literal.rs @@ -1,4 +1,4 @@ -use ruff_formatter::FormatRuleWithOptions; +use ruff_formatter::{FormatRuleWithOptions, GroupId}; use ruff_python_ast::{AnyNodeRef, ExprStringLiteral}; use crate::expression::parentheses::{ @@ -10,14 +10,29 @@ use crate::string::{AnyString, FormatImplicitConcatenatedString}; #[derive(Default)] pub struct FormatExprStringLiteral { - kind: StringLiteralKind, + layout: ExprStringLiteralLayout, +} + +#[derive(Default)] +pub struct ExprStringLiteralLayout { + pub kind: StringLiteralKind, + pub implicit_group_id: Option, +} + +impl ExprStringLiteralLayout { + pub const fn docstring() -> Self { + Self { + kind: StringLiteralKind::Docstring, + implicit_group_id: None, + } + } } impl FormatRuleWithOptions> for FormatExprStringLiteral { - type Options = StringLiteralKind; + type Options = ExprStringLiteralLayout; fn with_options(mut self, options: Self::Options) -> Self { - self.kind = options; + self.layout = options; self } } @@ -27,15 +42,23 @@ impl FormatNodeRule for FormatExprStringLiteral { let ExprStringLiteral { value, .. } = item; match value.as_slice() { - [string_literal] => string_literal.format().with_options(self.kind).fmt(f), + [string_literal] => string_literal + .format() + .with_options(self.layout.kind) + .fmt(f), _ => { // This is just a sanity check because [`DocstringStmt::try_from_statement`] // ensures that the docstring is a *single* string literal. - assert!(!self.kind.is_docstring()); + assert!(!self.layout.kind.is_docstring()); - in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)) + match self.layout.implicit_group_id { + Some(group_id) => group(&FormatImplicitConcatenatedString::new(item)) + .with_group_id(Some(group_id)) + .fmt(f), + None => in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)) + .fmt(f), + } } - .fmt(f), } } } @@ -48,9 +71,10 @@ impl NeedsParentheses for ExprStringLiteral { ) -> OptionalParentheses { // TODO: This is complicated. We should only return multiline if we *know* that // it won't fit because of how assignment works. - if self.value.is_implicit_concatenated() { - OptionalParentheses::Multiline - } else if AnyString::String(self).is_multiline(context.source()) { + // if self.value.is_implicit_concatenated() { + // OptionalParentheses::Multiline + // } else + if AnyString::String(self).is_multiline(context.source()) { OptionalParentheses::Never } else { OptionalParentheses::BestFit diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 1cc060ec114234..6ccb5eb9695633 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -768,15 +768,18 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> { Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) if value.is_implicit_concatenated() => { - self.update_max_precedence(OperatorPrecedence::String); + // FIXME make this a preview only change + // self.update_max_precedence(OperatorPrecedence::String); + return; } Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) if value.is_implicit_concatenated() => { - self.update_max_precedence(OperatorPrecedence::String); + // self.update_max_precedence(OperatorPrecedence::String); + return; } Expr::FString(ast::ExprFString { value, .. }) if value.is_implicit_concatenated() => { - self.update_max_precedence(OperatorPrecedence::String); + // self.update_max_precedence(OperatorPrecedence::String); return; } @@ -1255,7 +1258,7 @@ pub(crate) fn is_splittable_expression(expr: &Expr, context: &PyFormatContext) - // 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::StringLiteral(string) => false, 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. diff --git a/crates/ruff_python_formatter/src/other/arguments.rs b/crates/ruff_python_formatter/src/other/arguments.rs index 438989fdb7639f..a3d1f34d494a10 100644 --- a/crates/ruff_python_formatter/src/other/arguments.rs +++ b/crates/ruff_python_formatter/src/other/arguments.rs @@ -223,6 +223,8 @@ fn is_huggable_string_argument( arguments: &Arguments, context: &PyFormatContext, ) -> bool { + // TODO: Implicit concatenated could become regular string. Although not if it is multiline. So that should be fine? + // Double check if string.is_implicit_concatenated() || !string.is_multiline(context.source()) { return false; } diff --git a/crates/ruff_python_formatter/src/pattern/mod.rs b/crates/ruff_python_formatter/src/pattern/mod.rs index d564a6f97025a8..a85b517cc9ef87 100644 --- a/crates/ruff_python_formatter/src/pattern/mod.rs +++ b/crates/ruff_python_formatter/src/pattern/mod.rs @@ -289,18 +289,22 @@ impl<'a> CanOmitOptionalParenthesesVisitor<'a> { Pattern::MatchValue(value) => match &*value.value { Expr::StringLiteral(string) => { - self.update_max_precedence(OperatorPrecedence::String, string.value.len()); + // TODO update? + + // self.update_max_precedence(OperatorPrecedence::String, string.value.len()); } Expr::BytesLiteral(bytes) => { - self.update_max_precedence(OperatorPrecedence::String, bytes.value.len()); + // TODO update? + // self.update_max_precedence(OperatorPrecedence::String, bytes.value.len()); } // F-strings are allowed according to python's grammar but fail with a syntax error at runtime. // That's why we need to support them for formatting. Expr::FString(string) => { - self.update_max_precedence( - OperatorPrecedence::String, - string.value.as_slice().len(), - ); + // TODO update? + // self.update_max_precedence( + // OperatorPrecedence::String, + // string.value.as_slice().len(), + // ); } Expr::NumberLiteral(_) | Expr::Attribute(_) | Expr::UnaryOp(_) => { diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index 3e4da62aedd849..5f8011c35e73ec 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -8,6 +8,9 @@ use crate::comments::{ trailing_comments, Comments, LeadingDanglingTrailingComments, SourceComment, }; use crate::context::{NodeLevel, WithNodeLevel}; +use crate::expression::expr_bytes_literal::ExprBytesLiteralLayout; +use crate::expression::expr_f_string::ExprFStringLayout; +use crate::expression::expr_string_literal::ExprStringLiteralLayout; use crate::expression::parentheses::{ is_expression_parenthesized, optional_parentheses, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, @@ -16,7 +19,9 @@ use crate::expression::{ can_omit_optional_parentheses, has_own_parentheses, has_parentheses, maybe_parenthesize_expression, }; +use crate::other::string_literal::StringLiteralKind; use crate::statement::trailing_semicolon; +use crate::string::AnyString; use crate::{has_skip_comment, prelude::*}; #[derive(Default)] @@ -299,30 +304,103 @@ impl Format> for FormatStatementsLastExpression<'_> { *statement, &comments, ) { + let is_implicit_concatenated = AnyString::try_from(*value) + .is_ok_and(|string| string.is_implicit_concatenated()); + let group_id = f.group_id("optional_parentheses"); let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f); - best_fit_parenthesize(&format_with(|f| { - inline_comments.mark_formatted(); - - value.format().with_options(Parentheses::Never).fmt(f)?; + if is_implicit_concatenated { + let implicit_group_id = f.group_id("implicit_concatenated"); + best_fit_parenthesize(&format_with(|f| { + inline_comments.mark_formatted(); + + match value { + Expr::StringLiteral(literal) => { + assert!(literal.value.is_implicit_concatenated()); + + literal + .format() + .with_options(ExprStringLiteralLayout { + kind: StringLiteralKind::String, + implicit_group_id: Some(implicit_group_id), + }) + .fmt(f)?; + } + Expr::BytesLiteral(literal) => { + assert!(literal.value.is_implicit_concatenated()); + + literal + .format() + .with_options(ExprBytesLiteralLayout { + implicit_group_id: Some(implicit_group_id), + }) + .fmt(f)?; + } + + Expr::FString(literal) => { + assert!(literal.value.is_implicit_concatenated()); + + literal + .format() + .with_options(ExprFStringLayout { + implicit_group_id: Some(implicit_group_id), + }) + .fmt(f)?; + } + + _ => { + unreachable!( + "Should only be called for implicit concatenated strings." + ) + } + } + + if !inline_comments.is_empty() { + // If the implicit concatenated string fits in a single line,, format the comment in parentheses + if_group_fits_on_line(&inline_comments) + .with_group_id(Some(implicit_group_id)) + .fmt(f)?; + } + + Ok(()) + })) + .with_group_id(Some(group_id)) + .fmt(f)?; if !inline_comments.is_empty() { - // If the expressions exceeds the line width, format the comments in the parentheses - if_group_breaks(&inline_comments).fmt(f)?; + // If the implicit concatenated string expands, format the comments outside the parentheses + if_group_breaks(&inline_comments) + .with_group_id(Some(implicit_group_id)) + .fmt(f)?; } + } else { + best_fit_parenthesize(&format_once(|f| { + inline_comments.mark_formatted(); + + // Can we just call `FormatImplicitString` here with a custom layout assigns a group id + // that we can reference in `if_group_breaks` and `if_group_fits_on_line`. + // The other alternative is that this code creates the `FormatImplicitStringGroup` and by-passes + // calling `FormatStringLiteralExpression` directly. + value.format().with_options(Parentheses::Never).fmt(f)?; - Ok(()) - })) - .with_group_id(Some(group_id)) - .fmt(f)?; + if !inline_comments.is_empty() { + // If the expressions exceeds the line width, format the comments in the parentheses + if_group_breaks(&inline_comments).fmt(f)?; + } - if !inline_comments.is_empty() { - // If the line fits into the line width, format the comments after the parenthesized expression - if_group_fits_on_line(&inline_comments) - .with_group_id(Some(group_id)) - .fmt(f)?; + Ok(()) + })) + .with_group_id(Some(group_id)) + .fmt(f)?; + + if !inline_comments.is_empty() { + // If the line fits into the line width, format the comments after the parenthesized expression + if_group_fits_on_line(&inline_comments) + .with_group_id(Some(group_id)) + .fmt(f)?; + } } Ok(()) diff --git a/crates/ruff_python_formatter/src/statement/suite.rs b/crates/ruff_python_formatter/src/statement/suite.rs index c483f917e2395c..5bcdeb2659c72a 100644 --- a/crates/ruff_python_formatter/src/statement/suite.rs +++ b/crates/ruff_python_formatter/src/statement/suite.rs @@ -11,6 +11,7 @@ use crate::comments::{ leading_comments, trailing_comments, Comments, LeadingDanglingTrailingComments, }; use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel}; +use crate::expression::expr_string_literal::ExprStringLiteralLayout; use crate::other::string_literal::StringLiteralKind; use crate::prelude::*; use crate::statement::stmt_expr::FormatStmtExpr; @@ -850,7 +851,7 @@ impl Format> for DocstringStmt<'_> { .then_some(source_position(self.docstring.start())), string_literal .format() - .with_options(StringLiteralKind::Docstring), + .with_options(ExprStringLiteralLayout::docstring()), f.options() .source_map_generation() .is_enabled() diff --git a/crates/ruff_python_formatter/src/string/any.rs b/crates/ruff_python_formatter/src/string/any.rs index b36bdf7556d552..5b72fa2a49e255 100644 --- a/crates/ruff_python_formatter/src/string/any.rs +++ b/crates/ruff_python_formatter/src/string/any.rs @@ -2,6 +2,7 @@ use std::iter::FusedIterator; use memchr::memchr2; +use ruff_formatter::Format; use ruff_python_ast::{ self as ast, AnyNodeRef, AnyStringFlags, Expr, ExprBytesLiteral, ExprFString, ExprStringLiteral, ExpressionRef, FStringElement, FStringPart, StringFlags, StringLiteral, @@ -12,6 +13,8 @@ use ruff_text_size::{Ranged, TextRange}; use crate::expression::expr_f_string::f_string_quoting; use crate::string::Quoting; +use super::{PyFormatContext, PyFormatter}; + /// Represents any kind of string expression. This could be either a string, /// bytes or f-string. #[derive(Copy, Clone, Debug)] @@ -134,6 +137,19 @@ impl<'a> From<&'a ExprFString> for AnyString<'a> { } } +impl<'a> TryFrom<&'a Expr> for AnyString<'a> { + type Error = (); + + fn try_from(value: &'a Expr) -> Result { + match value { + Expr::StringLiteral(string) => Ok(AnyString::String(string)), + Expr::BytesLiteral(bytes) => Ok(AnyString::Bytes(bytes)), + Expr::FString(fstring) => Ok(AnyString::FString(fstring)), + _ => Err(()), + } + } +} + #[derive(Clone)] pub(super) enum AnyStringPartsIter<'a> { String(std::slice::Iter<'a, StringLiteral>), diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index ffed2a17517f7d..4830c447dea5ae 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -143,7 +143,7 @@ impl Format> for FormatImplicitConcatenatedString<'_> { joiner.finish() }); - if let Some(collapsed_quotes) = dbg!(self.merged_prefix(f.context())) { + if let Some(collapsed_quotes) = self.merged_prefix(f.context()) { let format_flat = format_with(|f| { let mut parts = self.string.parts(); diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__fmtonoff5.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__fmtonoff5.py.snap index 37aedd195561c4..a0ab4db2e524db 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__fmtonoff5.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__fmtonoff5.py.snap @@ -104,7 +104,7 @@ elif unformatted: - "=foo.bar.:main", - # fmt: on - ] # Includes an formatted indentation. -+ "foo-bar" "=foo.bar.:main", ++ "foo-bar=foo.bar.:main", + # fmt: on + ] # Includes an formatted indentation. }, @@ -128,7 +128,7 @@ setup( entry_points={ # fmt: off "console_scripts": [ - "foo-bar" "=foo.bar.:main", + "foo-bar=foo.bar.:main", # fmt: on ] # Includes an formatted indentation. }, diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary_implicit_string.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary_implicit_string.py.snap index 5f3c84a8dfa691..dd213abd4f2708 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__binary_implicit_string.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__binary_implicit_string.py.snap @@ -398,12 +398,9 @@ c = ( "dddddddddddddddddddddddddd" % aaaaaaaaaaaa + x ) -"a" "b" "c" + "d" "e" + "f" "g" + "h" "i" "j" +"abc" + "de" + "fg" + "hij" class EC2REPATH: - f.write("Pathway name" + "\t" "Database Identifier" + "\t" "Source database" + "\n") + f.write("Pathway name" + "\tDatabase Identifier" + "\tSource database" + "\n") ``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__bytes.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__bytes.py.snap index 7f980687824931..3453b5e25016cb 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__bytes.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__bytes.py.snap @@ -279,7 +279,7 @@ test_particular = [ ] # Parenthesized string continuation with messed up indentation -{"key": ([], b"a" b"b" b"c")} +{"key": ([], b"abc")} b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}" ``` @@ -435,10 +435,7 @@ test_particular = [ ] # Parenthesized string continuation with messed up indentation -{'key': ([], b'a' b'b' b'c')} +{'key': ([], b'abc')} b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}" ``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap index 5faebb836e37df..cbc1bbfe6e9806 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap @@ -340,7 +340,7 @@ source_type = Python ``` ```python -(f"{one}" f"{two}") +(f"{one}{two}") rf"Not-so-tricky \"quote" @@ -380,7 +380,7 @@ result_f = ( ) ( - f"{1}" f"{2}" # comment 3 + f"{1}{2}" # comment 3 ) ( @@ -684,7 +684,7 @@ source_type = Python ``` ```python -(f"{one}" f"{two}") +(f"{one}{two}") rf"Not-so-tricky \"quote" @@ -724,7 +724,7 @@ result_f = ( ) ( - f"{1}" f"{2}" # comment 3 + f"{1}{2}" # comment 3 ) ( diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__string.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__string.py.snap index 96a35c577ad565..02580b42827c02 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__string.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__string.py.snap @@ -304,7 +304,7 @@ test_particular = [ ] # Parenthesized string continuation with messed up indentation -{"key": ([], "a" "b" "c")} +{"key": ([], "abc")} # Regression test for https://github.com/astral-sh/ruff/issues/5893 @@ -488,7 +488,7 @@ test_particular = [ ] # Parenthesized string continuation with messed up indentation -{'key': ([], 'a' 'b' 'c')} +{'key': ([], 'abc')} # Regression test for https://github.com/astral-sh/ruff/issues/5893 @@ -513,6 +513,3 @@ a = """\x1f""" a = """\\x1F""" a = """\\\x1f""" ``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap index 0d591cc737f80a..c7913fba5ae05e 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap @@ -209,7 +209,7 @@ yield ( ) ) -yield "Cache key will cause errors if used with memcached: %r " "(longer than %s)" % ( +yield "Cache key will cause errors if used with memcached: %r (longer than %s)" % ( key, MEMCACHE_MAX_KEY_LENGTH, ) @@ -227,22 +227,12 @@ yield ( "Django to create, modify, and delete the table" ) yield ( - "# Feel free to rename the models, but don't rename db_table values or " - "field names." + "# Feel free to rename the models, but don't rename db_table values or field names." ) -yield ( - "# * Make sure each ForeignKey and OneToOneField has `on_delete` set " - "to the desired behavior" -) -yield ( - "# * Remove `managed = False` lines if you wish to allow " - "Django to create, modify, and delete the table" -) -yield ( - "# Feel free to rename the models, but don't rename db_table values or " - "field names." -) +yield "# * Make sure each ForeignKey and OneToOneField has `on_delete` set " "to the desired behavior" +yield "# * Remove `managed = False` lines if you wish to allow " "Django to create, modify, and delete the table" +yield "# Feel free to rename the models, but don't rename db_table values or " "field names." # Regression test for: https://github.com/astral-sh/ruff/issues/7420 result = yield self.request( @@ -277,6 +267,3 @@ result = yield ( print((yield x)) ``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@pattern__pattern_maybe_parenthesize.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@pattern__pattern_maybe_parenthesize.py.snap index 29004a1548d040..7a8e1705e61505 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@pattern__pattern_maybe_parenthesize.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@pattern__pattern_maybe_parenthesize.py.snap @@ -523,7 +523,7 @@ match x: ## Always use parentheses for implicitly concatenated strings match x: case ( - "implicit" "concatenated" "string" + "implicitconcatenatedstring" | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] ): pass @@ -531,7 +531,7 @@ match x: match x: case ( - b"implicit" b"concatenated" b"string" + b"implicitconcatenatedstring" | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] ): pass @@ -539,7 +539,7 @@ match x: match x: case ( - f"implicit" "concatenated" "string" + f"implicitconcatenatedstring" | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] ): pass @@ -831,7 +831,51 @@ match x: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccccccccccccccc, ): -@@ -246,63 +238,48 @@ +@@ -220,89 +212,80 @@ + + ## Always use parentheses for implicitly concatenated strings + match x: +- case ( +- "implicitconcatenatedstring" +- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] +- ): ++ case "implicitconcatenatedstring" | [ ++ aaaaaa, ++ bbbbbbbbbbbbbbbb, ++ cccccccccccccccccc, ++ ddddddddddddddddddddddddddd, ++ ]: + pass + + + match x: +- case ( +- b"implicitconcatenatedstring" +- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] +- ): ++ case b"implicitconcatenatedstring" | [ ++ aaaaaa, ++ bbbbbbbbbbbbbbbb, ++ cccccccccccccccccc, ++ ddddddddddddddddddddddddddd, ++ ]: + pass + + + match x: +- case ( +- f"implicitconcatenatedstring" +- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] +- ): ++ case f"implicitconcatenatedstring" | [ ++ aaaaaa, ++ bbbbbbbbbbbbbbbb, ++ cccccccccccccccccc, ++ ddddddddddddddddddddddddddd, ++ ]: + pass + + ## Complex number expressions and unary expressions match x: diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_no_parameters.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_no_parameters.py.snap index 2032db23087010..aea12e49f4988b 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_no_parameters.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_no_parameters.py.snap @@ -266,12 +266,12 @@ def test_return_multiline_string_binary_expression_return_type_annotation() -> ( ######################################################################################### -def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb": +def test_implicit_concatenated_string_return_type() -> "strbbbbbbbbbbbbbbbb": pass def test_overlong_implicit_concatenated_string_return_type() -> ( - "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb" + "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb" ): pass diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_parameters.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_parameters.py.snap index ca5a99fc920b6f..a8d770d11fa017 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_parameters.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__return_type_parameters.py.snap @@ -287,13 +287,13 @@ def test_return_multiline_string_binary_expression_return_type_annotation( ######################################################################################### -def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb": +def test_implicit_concatenated_string_return_type(a) -> "strbbbbbbbbbbbbbbbb": pass def test_overlong_implicit_concatenated_string_return_type( a, -) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb": +) -> "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb": pass