Skip to content

Commit

Permalink
Use BestFitParenthesize layout for literals and match as
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Sep 25, 2024
1 parent ca0ae0a commit 94a294b
Show file tree
Hide file tree
Showing 8 changed files with 520 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Patterns that use BestFit should be parenthesized if they exceed the configured line width
# but fit within parentheses.
match x:
case (
"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPar"
):
pass


match x:
case (
b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
):
pass

match x:
case (
f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
):
pass


match x:
case (
5444444444444444444444444444444444444444444444444444444444444444444444444444444j
):
pass


match x:
case (
5444444444444444444444444444444444444444444444444444444444444444444444444444444
):
pass


match x:
case (
5.44444444444444444444444444444444444444444444444444444444444444444444444444444
):
pass


match x:
case (
averyLongIdentThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenth
):
pass


# But they aren't parenthesized when they exceed the line length even parenthesized
match x:
case "averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenthesized":
pass


match x:
case b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenthesized":
pass

match x:
case f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenthesized":
pass


match x:
case 54444444444444444444444444444444444444444444444444444444444444444444444444444444444j:
pass


match x:
case 5444444444444444444444444444444444444444444444444444444444444444444444444444444444:
pass


match x:
case 5.444444444444444444444444444444444444444444444444444444444444444444444444444444444:
pass


match x:
case averyLongIdentifierThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenthesized:
pass


# It uses the Multiline layout when there's an alias.
match x:
case (
averyLongIdentifierThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsParenthe as b
):
pass



match x:
case (
"an implicit concatenated" "string literal" "in a match case" "that goes over multiple lines"
):
pass


55 changes: 29 additions & 26 deletions crates/ruff_python_formatter/src/other/match_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use ruff_python_ast::MatchCase;

use crate::builders::parenthesize_if_expands;
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
use crate::pattern::maybe_parenthesize_pattern;
use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
use crate::statement::suite::SuiteKind;

Expand Down Expand Up @@ -34,39 +36,40 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
let comments = f.context().comments().clone();
let dangling_item_comments = comments.dangling(item);

let format_pattern = format_with(|f| {
if is_match_case_parentheses_enabled(f.context()) {
maybe_parenthesize_pattern(pattern, item).fmt(f)
} else {
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 {
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 | OptionalParentheses::BestFit => {
pattern.format().with_options(Parentheses::Never).fmt(f)
}
}
}
}
});

write!(
f,
[
clause_header(
ClauseHeader::MatchCase(item),
dangling_item_comments,
&format_with(|f| {
write!(f, [token("case"), space()])?;

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 {
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)?;
}
}
}
write!(f, [token("case"), space(), format_pattern])?;

if let Some(guard) = guard {
write!(f, [space(), token("if"), space(), guard.format()])?;
Expand Down
73 changes: 71 additions & 2 deletions crates/ruff_python_formatter/src/pattern/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions};
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::Pattern;
use ruff_python_ast::{MatchCase, Pattern};
use ruff_python_trivia::CommentRanges;
use ruff_python_trivia::{
first_non_trivia_token, BackwardsTokenizer, SimpleToken, SimpleTokenKind,
};
use ruff_text_size::Ranged;

use crate::builders::parenthesize_if_expands;
use crate::context::{NodeLevel, WithNodeLevel};
use crate::expression::parentheses::{
parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
optional_parentheses, parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
};
use crate::prelude::*;

Expand Down Expand Up @@ -150,3 +152,70 @@ impl NeedsParentheses for Pattern {
}
}
}

pub(crate) fn maybe_parenthesize_pattern<'a>(
pattern: &'a Pattern,
case: &'a MatchCase,
) -> MaybeParenthesizePattern<'a> {
MaybeParenthesizePattern { pattern, case }
}

#[derive(Debug)]
pub(crate) struct MaybeParenthesizePattern<'a> {
pattern: &'a Pattern,
case: &'a MatchCase,
}

impl Format<PyFormatContext<'_>> for MaybeParenthesizePattern<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let MaybeParenthesizePattern { pattern, case } = self;

let comments = f.context().comments();
let pattern_comments = comments.leading_dangling_trailing(*pattern);

// If the pattern 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 pattern_comments.has_leading() || pattern_comments.has_trailing_own_line() {
return pattern.format().with_options(Parentheses::Always).fmt(f);
}

let needs_parentheses = pattern.needs_parentheses(AnyNodeRef::from(*case), f.context());

match needs_parentheses {
OptionalParentheses::Always => {
pattern.format().with_options(Parentheses::Always).fmt(f)
}
OptionalParentheses::Never => pattern.format().with_options(Parentheses::Never).fmt(f),
OptionalParentheses::Multiline => {
if can_pattern_omit_optional_parentheses(pattern, f.context()) {
optional_parentheses(&pattern.format().with_options(Parentheses::Never)).fmt(f)
} else {
parenthesize_if_expands(&pattern.format().with_options(Parentheses::Never))
.fmt(f)
}
}
OptionalParentheses::BestFit => {
if pattern_comments.has_trailing() {
pattern.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(&pattern.format().with_options(Parentheses::Never))
.with_group_id(Some(group_id))
.fmt(f)
}
}
}
}
}

pub(crate) fn can_pattern_omit_optional_parentheses(
_pattern: &Pattern,
_context: &PyFormatContext,
) -> bool {
// TODO Implement
false
}
13 changes: 11 additions & 2 deletions crates/ruff_python_formatter/src/pattern/pattern_match_as.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ruff_python_ast::PatternMatchAs;
use crate::comments::dangling_comments;
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;

#[derive(Default)]
pub struct FormatPatternMatchAs;
Expand Down Expand Up @@ -54,8 +55,16 @@ impl NeedsParentheses for PatternMatchAs {
fn needs_parentheses(
&self,
_parent: AnyNodeRef,
_context: &PyFormatContext,
context: &PyFormatContext,
) -> OptionalParentheses {
OptionalParentheses::Multiline
if is_match_case_parentheses_enabled(context) {
if self.name.is_some() {
OptionalParentheses::Multiline
} else {
OptionalParentheses::BestFit
}
} else {
OptionalParentheses::Multiline
}
}
}
11 changes: 8 additions & 3 deletions crates/ruff_python_formatter/src/pattern/pattern_match_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ruff_python_ast::PatternMatchValue;

use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;

#[derive(Default)]
pub struct FormatPatternMatchValue;
Expand All @@ -17,9 +18,13 @@ impl FormatNodeRule<PatternMatchValue> for FormatPatternMatchValue {
impl NeedsParentheses for PatternMatchValue {
fn needs_parentheses(
&self,
_parent: AnyNodeRef,
_context: &PyFormatContext,
parent: AnyNodeRef,
context: &PyFormatContext,
) -> OptionalParentheses {
OptionalParentheses::Never
if is_match_case_parentheses_enabled(context) {
self.value.needs_parentheses(parent, context)
} else {
OptionalParentheses::Never
}
}
}
6 changes: 6 additions & 0 deletions crates/ruff_python_formatter/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ pub(crate) fn is_empty_parameters_no_unnecessary_parentheses_around_return_value
) -> bool {
context.is_preview()
}

/// See [#6933](https:/astral-sh/ruff/issues/6933).
/// This style also covers the black preview styles `remove_redundant_guard_parens` and `parens_for_long_if_clauses_in_case_block `.
pub(crate) fn is_match_case_parentheses_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
Loading

0 comments on commit 94a294b

Please sign in to comment.