diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/match.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/match.py index 8b45e6a9ff..3cb88f858b 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/match.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/match.py @@ -263,6 +263,7 @@ match foo: ): y = 1 + match foo: case [1, 2, *rest]: pass @@ -299,3 +300,47 @@ match foo: _, 1, 2]: pass + +match foo: + case (1): + pass + case ((1)): + pass + case [(1), 2]: + pass + case [( # comment + 1 + ), 2]: + pass + case [ # outer + ( # inner + 1 + ), 2]: + pass + case [ + ( # outer + [ # inner + 1, + ] + ) + ]: + pass + case [ # outer + ( # inner outer + [ # inner + 1, + ] + ) + ]: + pass + case [ # outer + # own line + ( # inner outer + [ # inner + 1, + ] + ) + ]: + pass + case [(*rest), (a as b)]: + pass diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 56a9c35e84..6d96c942a2 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -212,7 +212,6 @@ fn handle_enclosed_comment<'a>( handle_leading_class_with_decorators_comment(comment, class_def) } AnyNodeRef::StmtImportFrom(import_from) => handle_import_from_comment(comment, import_from), - AnyNodeRef::MatchCase(match_case) => handle_match_case_comment(comment, match_case), AnyNodeRef::StmtWith(with_) => handle_with_comment(comment, with_), AnyNodeRef::ExprConstant(_) => { if let Some(AnyNodeRef::ExprFString(fstring)) = comment.enclosing_parent() { @@ -1361,28 +1360,6 @@ fn handle_import_from_comment<'a>( } } -/// Attach an enclosed end-of-line comment to a [`MatchCase`]. -/// -/// For example, given: -/// ```python -/// case ( # comment -/// pattern -/// ): -/// ... -/// ``` -/// -/// The comment will be attached to the [`MatchCase`] node as a dangling comment. -fn handle_match_case_comment<'a>( - comment: DecoratedComment<'a>, - match_case: &'a MatchCase, -) -> CommentPlacement<'a> { - if comment.line_position().is_end_of_line() && comment.start() < match_case.pattern.start() { - CommentPlacement::dangling(comment.enclosing_node(), comment) - } else { - CommentPlacement::Default(comment) - } -} - /// Attach an enclosed end-of-line comment to a [`ast::StmtWith`]. /// /// For example, given: diff --git a/crates/ruff_python_formatter/src/other/match_case.rs b/crates/ruff_python_formatter/src/other/match_case.rs index c1992aa37b..89c21bffa7 100644 --- a/crates/ruff_python_formatter/src/other/match_case.rs +++ b/crates/ruff_python_formatter/src/other/match_case.rs @@ -1,13 +1,13 @@ use ruff_formatter::{write, Buffer, FormatResult}; -use ruff_python_ast::{MatchCase, Pattern, Ranged}; -use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; -use ruff_text_size::TextRange; +use ruff_python_ast::node::AstNode; +use ruff_python_ast::MatchCase; +use crate::builders::parenthesize_if_expands; use crate::comments::SourceComment; -use crate::expression::parentheses::parenthesized; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; use crate::statement::clause::{clause_body, clause_header, ClauseHeader}; -use crate::{FormatError, FormatNodeRule, PyFormatter}; +use crate::{FormatNodeRule, PyFormatter}; #[derive(Default)] pub struct FormatMatchCase; @@ -21,13 +21,8 @@ impl FormatNodeRule for FormatMatchCase { body, } = item; - // Distinguish dangling comments that appear on the open parenthesis from those that - // appear on the trailing colon. let comments = f.context().comments().clone(); let dangling_item_comments = comments.dangling(item); - let (open_parenthesis_comments, trailing_colon_comments) = dangling_item_comments.split_at( - dangling_item_comments.partition_point(|comment| comment.start() < pattern.start()), - ); write!( f, @@ -38,12 +33,29 @@ impl FormatNodeRule for FormatMatchCase { &format_with(|f| { write!(f, [text("case"), space()])?; - if is_match_case_pattern_parenthesized(item, pattern, f.context())? { - parenthesized("(", &pattern.format(), ")") - .with_dangling_comments(open_parenthesis_comments) - .fmt(f)?; + let has_comments = comments.has_leading(pattern) + || comments.has_trailing_own_line(pattern); + + if has_comments { + pattern.format().with_options(Parentheses::Always).fmt(f)?; } else { - pattern.format().fmt(f)?; + match pattern.needs_parentheses(item.as_any_node_ref(), f.context()) { + OptionalParentheses::Multiline => { + parenthesize_if_expands( + &pattern.format().with_options(Parentheses::Never), + ) + .fmt(f)?; + } + OptionalParentheses::Always => { + pattern.format().with_options(Parentheses::Always).fmt(f)?; + } + OptionalParentheses::Never => { + pattern.format().with_options(Parentheses::Never).fmt(f)?; + } + OptionalParentheses::BestFit => { + pattern.format().with_options(Parentheses::Never).fmt(f)?; + } + } } if let Some(guard) = guard { @@ -53,7 +65,7 @@ impl FormatNodeRule for FormatMatchCase { Ok(()) }), ), - clause_body(body, trailing_colon_comments), + clause_body(body, dangling_item_comments), ] ) } @@ -67,33 +79,3 @@ impl FormatNodeRule for FormatMatchCase { Ok(()) } } - -fn is_match_case_pattern_parenthesized( - case: &MatchCase, - pattern: &Pattern, - context: &PyFormatContext, -) -> FormatResult { - let mut tokenizer = SimpleTokenizer::new( - context.source(), - TextRange::new(case.start(), pattern.start()), - ) - .skip_trivia(); - - let case_keyword = tokenizer.next().ok_or(FormatError::syntax_error( - "Expected a `case` keyword, didn't find any token", - ))?; - - debug_assert_eq!( - case_keyword.kind(), - SimpleTokenKind::Case, - "Expected `case` keyword but at {case_keyword:?}" - ); - - match tokenizer.next() { - Some(left_paren) => { - debug_assert_eq!(left_paren.kind(), SimpleTokenKind::LParen); - Ok(true) - } - None => Ok(false), - } -} diff --git a/crates/ruff_python_formatter/src/pattern/mod.rs b/crates/ruff_python_formatter/src/pattern/mod.rs index 9099772a4d..35c6eca1e6 100644 --- a/crates/ruff_python_formatter/src/pattern/mod.rs +++ b/crates/ruff_python_formatter/src/pattern/mod.rs @@ -1,6 +1,11 @@ -use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule}; -use ruff_python_ast::Pattern; +use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions}; +use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::{Pattern, Ranged}; +use ruff_python_trivia::{first_non_trivia_token, SimpleToken, SimpleTokenKind, SimpleTokenizer}; +use crate::expression::parentheses::{ + parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, +}; use crate::prelude::*; pub(crate) mod pattern_match_as; @@ -12,20 +17,64 @@ pub(crate) mod pattern_match_singleton; pub(crate) mod pattern_match_star; pub(crate) mod pattern_match_value; -#[derive(Default)] -pub struct FormatPattern; +#[derive(Copy, Clone, PartialEq, Eq, Default)] +pub struct FormatPattern { + parentheses: Parentheses, +} + +impl FormatRuleWithOptions> for FormatPattern { + type Options = Parentheses; + + fn with_options(mut self, options: Self::Options) -> Self { + self.parentheses = options; + self + } +} impl FormatRule> for FormatPattern { - fn fmt(&self, item: &Pattern, f: &mut PyFormatter) -> FormatResult<()> { - match item { - Pattern::MatchValue(p) => p.format().fmt(f), - Pattern::MatchSingleton(p) => p.format().fmt(f), - Pattern::MatchSequence(p) => p.format().fmt(f), - Pattern::MatchMapping(p) => p.format().fmt(f), - Pattern::MatchClass(p) => p.format().fmt(f), - Pattern::MatchStar(p) => p.format().fmt(f), - Pattern::MatchAs(p) => p.format().fmt(f), - Pattern::MatchOr(p) => p.format().fmt(f), + fn fmt(&self, pattern: &Pattern, f: &mut PyFormatter) -> FormatResult<()> { + let format_pattern = format_with(|f| match pattern { + Pattern::MatchValue(pattern) => pattern.format().fmt(f), + Pattern::MatchSingleton(pattern) => pattern.format().fmt(f), + Pattern::MatchSequence(pattern) => pattern.format().fmt(f), + Pattern::MatchMapping(pattern) => pattern.format().fmt(f), + Pattern::MatchClass(pattern) => pattern.format().fmt(f), + Pattern::MatchStar(pattern) => pattern.format().fmt(f), + Pattern::MatchAs(pattern) => pattern.format().fmt(f), + Pattern::MatchOr(pattern) => pattern.format().fmt(f), + }); + + let parenthesize = match self.parentheses { + Parentheses::Preserve => is_pattern_parenthesized(pattern, f.context().source()), + Parentheses::Always => true, + Parentheses::Never => false, + }; + + if parenthesize { + let comments = f.context().comments().clone(); + + // Any comments on the open parenthesis. + // + // For example, `# comment` in: + // ```python + // ( # comment + // 1 + // ) + // ``` + let open_parenthesis_comment = comments + .leading(pattern) + .first() + .filter(|comment| comment.line_position().is_end_of_line()); + + parenthesized("(", &format_pattern, ")") + .with_dangling_comments( + open_parenthesis_comment + .map(std::slice::from_ref) + .unwrap_or_default(), + ) + .fmt(f) + } else { + format_pattern.fmt(f) } } } @@ -34,7 +83,7 @@ impl<'ast> AsFormat> for Pattern { type Format<'a> = FormatRefWithRule<'a, Pattern, FormatPattern, PyFormatContext<'ast>>; fn format(&self) -> Self::Format<'_> { - FormatRefWithRule::new(self, FormatPattern) + FormatRefWithRule::new(self, FormatPattern::default()) } } @@ -42,6 +91,49 @@ impl<'ast> IntoFormat> for Pattern { type Format = FormatOwnedWithRule>; fn into_format(self) -> Self::Format { - FormatOwnedWithRule::new(self, FormatPattern) + FormatOwnedWithRule::new(self, FormatPattern::default()) + } +} + +fn is_pattern_parenthesized(pattern: &Pattern, contents: &str) -> bool { + // First test if there's a closing parentheses because it tends to be cheaper. + if matches!( + first_non_trivia_token(pattern.end(), contents), + Some(SimpleToken { + kind: SimpleTokenKind::RParen, + .. + }) + ) { + let mut tokenizer = + SimpleTokenizer::up_to_without_back_comment(pattern.start(), contents).skip_trivia(); + + matches!( + tokenizer.next_back(), + Some(SimpleToken { + kind: SimpleTokenKind::LParen, + .. + }) + ) + } else { + false + } +} + +impl NeedsParentheses for Pattern { + fn needs_parentheses( + &self, + parent: AnyNodeRef, + context: &PyFormatContext, + ) -> OptionalParentheses { + match self { + Pattern::MatchValue(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchSingleton(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchSequence(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchMapping(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchClass(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchStar(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchAs(pattern) => pattern.needs_parentheses(parent, context), + Pattern::MatchOr(pattern) => pattern.needs_parentheses(parent, context), + } } } diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs index fb969c6b4b..c6dc298e3e 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_as.rs @@ -1,8 +1,9 @@ use ruff_formatter::{write, Buffer, FormatResult}; -use ruff_python_ast::{Pattern, PatternMatchAs}; +use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::PatternMatchAs; use crate::comments::{dangling_comments, SourceComment}; -use crate::expression::parentheses::parenthesized; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::prelude::*; use crate::{FormatNodeRule, PyFormatter}; @@ -21,18 +22,7 @@ impl FormatNodeRule for FormatPatternMatchAs { if let Some(name) = name { if let Some(pattern) = pattern { - // Parenthesize nested `PatternMatchAs` like `(a as b) as c`. - if matches!( - pattern.as_ref(), - Pattern::MatchAs(PatternMatchAs { - pattern: Some(_), - .. - }) - ) { - parenthesized("(", &pattern.format(), ")").fmt(f)?; - } else { - pattern.format().fmt(f)?; - } + pattern.format().fmt(f)?; if comments.has_trailing(pattern.as_ref()) { write!(f, [hard_line_break()])?; @@ -68,3 +58,13 @@ impl FormatNodeRule for FormatPatternMatchAs { Ok(()) } } + +impl NeedsParentheses for PatternMatchAs { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Multiline + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs index 6d8601b95e..84d11384e0 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_class.rs @@ -1,6 +1,9 @@ use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchClass; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::prelude::*; use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; #[derive(Default)] @@ -17,3 +20,13 @@ impl FormatNodeRule for FormatPatternMatchClass { ) } } + +impl NeedsParentheses for PatternMatchClass { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs index a98abc4cc8..cc810e4a7d 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_mapping.rs @@ -1,6 +1,9 @@ use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchMapping; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::prelude::*; use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; #[derive(Default)] @@ -17,3 +20,13 @@ impl FormatNodeRule for FormatPatternMatchMapping { ) } } + +impl NeedsParentheses for PatternMatchMapping { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs index ac19c02128..6d7e624ae8 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_or.rs @@ -1,6 +1,9 @@ use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchOr; +use crate::context::PyFormatContext; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; #[derive(Default)] @@ -17,3 +20,13 @@ impl FormatNodeRule for FormatPatternMatchOr { ) } } + +impl NeedsParentheses for PatternMatchOr { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Multiline + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs index 19db6f6953..4a6e39b663 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_sequence.rs @@ -1,9 +1,13 @@ use ruff_formatter::prelude::format_with; use ruff_formatter::{Format, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchSequence; use crate::builders::PyFormatterExtensions; -use crate::expression::parentheses::{empty_parenthesized, optional_parentheses, parenthesized}; +use crate::context::PyFormatContext; +use crate::expression::parentheses::{ + empty_parenthesized, optional_parentheses, parenthesized, NeedsParentheses, OptionalParentheses, +}; use crate::{FormatNodeRule, PyFormatter}; #[derive(Default)] @@ -51,3 +55,13 @@ impl FormatNodeRule for FormatPatternMatchSequence { } } } + +impl NeedsParentheses for PatternMatchSequence { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_singleton.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_singleton.rs index bd899ffc49..5017a9fa88 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_singleton.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_singleton.rs @@ -1,6 +1,8 @@ use crate::prelude::*; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::{Constant, PatternMatchSingleton}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::{FormatNodeRule, PyFormatter}; #[derive(Default)] @@ -16,3 +18,13 @@ impl FormatNodeRule for FormatPatternMatchSingleton { } } } + +impl NeedsParentheses for PatternMatchSingleton { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_star.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_star.rs index 7ee3dbffb8..35978eb386 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_star.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_star.rs @@ -1,9 +1,10 @@ -use ruff_formatter::{prelude::text, write, Buffer, FormatResult}; +use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchStar; use crate::comments::{dangling_comments, SourceComment}; -use crate::AsFormat; -use crate::{FormatNodeRule, PyFormatter}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::prelude::*; #[derive(Default)] pub struct FormatPatternMatchStar; @@ -32,3 +33,13 @@ impl FormatNodeRule for FormatPatternMatchStar { Ok(()) } } + +impl NeedsParentheses for PatternMatchStar { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never + } +} diff --git a/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs b/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs index 9dddc761f1..dc1b090b7a 100644 --- a/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs +++ b/crates/ruff_python_formatter/src/pattern/pattern_match_value.rs @@ -1,14 +1,26 @@ +use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::PatternMatchValue; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; +use crate::{AsFormat, FormatNodeRule, PyFormatter}; #[derive(Default)] pub struct FormatPatternMatchValue; impl FormatNodeRule for FormatPatternMatchValue { fn fmt_fields(&self, item: &PatternMatchValue, f: &mut PyFormatter) -> FormatResult<()> { - // TODO(charlie): Avoid double parentheses for parenthesized top-level `PatternMatchValue`. let PatternMatchValue { value, range: _ } = item; - value.format().fmt(f) + value.format().with_options(Parentheses::Never).fmt(f) + } +} + +impl NeedsParentheses for PatternMatchValue { + fn needs_parentheses( + &self, + _parent: AnyNodeRef, + _context: &PyFormatContext, + ) -> OptionalParentheses { + OptionalParentheses::Never } } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap index 1292cd47eb..6e81983223 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap @@ -231,7 +231,7 @@ match bar1: pass - case 4 as d, (5 as e), (6 | 7 as g), *h: -+ case 4 as d, 5 as e, NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as g, *h: ++ case 4 as d, (5 as e), (NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as g), *h: pass @@ -358,7 +358,7 @@ match something: case 2 as b, 3 as c: pass - case 4 as d, 5 as e, NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as g, *h: + case 4 as d, (5 as e), (NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as g), *h: pass diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_simple.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_simple.py.snap index 81ca3e9dc6..edbd357b65 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_simple.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_simple.py.snap @@ -117,13 +117,13 @@ def where_is(point): match command.split(): - case ["go", ("north" | "south" | "east" | "west")]: -+ case ["go", NOT_YET_IMPLEMENTED_PatternMatchOf | (y)]: ++ case ["go", (NOT_YET_IMPLEMENTED_PatternMatchOf | (y))]: current_room = current_room.neighbor(...) # how do I know which direction to go? match command.split(): - case ["go", ("north" | "south" | "east" | "west") as direction]: -+ case ["go", NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as direction]: ++ case ["go", (NOT_YET_IMPLEMENTED_PatternMatchOf | (y)) as direction]: current_room = current_room.neighbor(direction) match command.split(): @@ -223,12 +223,12 @@ match command.split(): ... # Code for picking up the given object match command.split(): - case ["go", NOT_YET_IMPLEMENTED_PatternMatchOf | (y)]: + case ["go", (NOT_YET_IMPLEMENTED_PatternMatchOf | (y))]: current_room = current_room.neighbor(...) # how do I know which direction to go? match command.split(): - case ["go", NOT_YET_IMPLEMENTED_PatternMatchOf | (y) as direction]: + case ["go", (NOT_YET_IMPLEMENTED_PatternMatchOf | (y)) as direction]: current_room = current_room.neighbor(direction) match command.split(): diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__match.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__match.py.snap index b3edf1348c..032a971380 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__match.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__match.py.snap @@ -269,6 +269,7 @@ match foo: ): y = 1 + match foo: case [1, 2, *rest]: pass @@ -305,6 +306,50 @@ match foo: _, 1, 2]: pass + +match foo: + case (1): + pass + case ((1)): + pass + case [(1), 2]: + pass + case [( # comment + 1 + ), 2]: + pass + case [ # outer + ( # inner + 1 + ), 2]: + pass + case [ + ( # outer + [ # inner + 1, + ] + ) + ]: + pass + case [ # outer + ( # inner outer + [ # inner + 1, + ] + ) + ]: + pass + case [ # outer + # own line + ( # inner outer + [ # inner + 1, + ] + ) + ]: + pass + case [(*rest), (a as b)]: + pass ``` ## Output @@ -440,7 +485,7 @@ match pattern_comments: match pattern_comments: - case (no_comments): + case no_comments: pass @@ -504,9 +549,7 @@ match pattern_singleton: # trailing own 2 ): pass - case ( - True # trailing - ): + case True: # trailing ... case False: ... @@ -524,7 +567,7 @@ match foo: pass case ["a", "b"]: pass - case (["a", "b"]): + case ["a", "b"]: pass @@ -545,29 +588,28 @@ match foo: match foo: case 1: y = 0 - case ((1)): + case 1: y = 1 - case (("a")): + case "a": y = 1 case ( # comment - (1) + 1 ): y = 1 case ( # comment - (1) + 1 ): y = 1 - case ( - (1) # comment - ): + case 1: # comment y = 1 case ( - (1) + 1 # comment ): y = 1 + match foo: case [1, 2, *rest]: pass @@ -627,6 +669,62 @@ match foo: 2, ]: pass + + +match foo: + case 1: + pass + case 1: + pass + case [(1), 2]: + pass + case [ + ( # comment + 1 + ), + 2, + ]: + pass + case [ + ( # outer + # inner + 1 + ), + 2, + ]: + pass + case [ + ( # outer + [ + # inner + 1, + ] + ) + ]: + pass + case [ + ( # outer + # inner outer + [ + # inner + 1, + ] + ) + ]: + pass + case [ + ( # outer + # own line + # inner outer + [ + # inner + 1, + ] + ) + ]: + pass + case [(*rest), (a as b)]: + pass ```