From 4ffd8172dfbc37b8d197a338aec88c27fd319066 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 7 Jul 2023 12:44:45 +0200 Subject: [PATCH] Add FitsExpanded and conditional group IR --- crates/ruff_formatter/src/builders.rs | 216 +++++++++++++++++- .../src/format_element/document.rs | 101 +++++++- .../ruff_formatter/src/format_element/tag.rs | 99 +++++++- crates/ruff_formatter/src/printer/mod.rs | 215 +++++++++++++---- .../src/comments/debug.rs | 6 +- 5 files changed, 581 insertions(+), 56 deletions(-) diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 441fb00a958d9..885e9f9ec5a41 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -1410,7 +1410,7 @@ impl Format for Group<'_, Context> { impl std::fmt::Debug for Group<'_, Context> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GroupElements") + f.debug_struct("Group") .field("group_id", &self.group_id) .field("should_expand", &self.should_expand) .field("content", &"{{content}}") @@ -1418,6 +1418,135 @@ impl std::fmt::Debug for Group<'_, Context> { } } +/// Sets the `condition` for the group. The element will behave as a regular group if `condition` is met, +/// and as *ungrouped* content if the condition is not met. +/// +/// ## Examples +/// +/// Only expand before operators if the parentheses are necessary. +/// +/// ``` +/// # use ruff_formatter::prelude::*; +/// # use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// +/// # fn main() -> FormatResult<()> { +/// use ruff_formatter::Formatted; +/// let content = format_with(|f| { /// +/// let parentheses_id = f.group_id("parentheses"); +/// group(&format_args![ +/// if_group_breaks(&text("(")), +/// indent_if_group_breaks(&format_args![ +/// soft_line_break(), +/// conditional_group(&format_args![ +/// text("'aaaaaaa'"), +/// soft_line_break_or_space(), +/// text("+"), +/// space(), +/// fits_expanded(&conditional_group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'Good morning!',"), +/// soft_line_break_or_space(), +/// text("'How are you?'"), +/// ]), +/// text("]"), +/// ], tag::Condition::if_group_fits_on_line(parentheses_id))), +/// soft_line_break_or_space(), +/// text("+"), +/// space(), +/// conditional_group(&format_args![ +/// text("'bbbb'"), +/// soft_line_break_or_space(), +/// text("and"), +/// space(), +/// text("'c'") +/// ], tag::Condition::if_group_fits_on_line(parentheses_id)) +/// ], tag::Condition::if_breaks()), +/// ], parentheses_id), +/// soft_line_break(), +/// if_group_breaks(&text(")")) +/// ]) +/// .with_group_id(Some(parentheses_id)) +/// .fmt(f) +/// }); +/// +/// let formatted = format!(SimpleFormatContext::default(), [content])?; +/// let document = formatted.into_document(); +/// +/// // All content fits +/// let all_fits = Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(65).unwrap(), +/// ..SimpleFormatOptions::default() +/// })); +/// +/// assert_eq!( +/// "'aaaaaaa' + ['Good morning!', 'How are you?'] + 'bbbb' and 'c'", +/// all_fits.print()?.as_code() +/// ); +/// +/// // The parentheses group fits, because it can expand the list, +/// let list_expanded = Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(21).unwrap(), +/// ..SimpleFormatOptions::default() +/// })); +/// +/// assert_eq!( +/// "'aaaaaaa' + [\n\t'Good morning!',\n\t'How are you?'\n] + 'bbbb' and 'c'", +/// list_expanded.print()?.as_code() +/// ); +/// +/// // It is necessary to split all groups to fit the content +/// let all_expanded = Formatted::new(document, SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(11).unwrap(), +/// ..SimpleFormatOptions::default() +/// })); +/// +/// assert_eq!( +/// "(\n\t'aaaaaaa'\n\t+ [\n\t\t'Good morning!',\n\t\t'How are you?'\n\t]\n\t+ 'bbbb'\n\tand 'c'\n)", +/// all_expanded.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn conditional_group( + content: &Content, + condition: Condition, +) -> ConditionalGroup +where + Content: Format, +{ + ConditionalGroup { + content: Argument::new(content), + condition, + } +} + +#[derive(Clone)] +pub struct ConditionalGroup<'content, Context> { + content: Argument<'content, Context>, + condition: Condition, +} + +impl Format for ConditionalGroup<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartConditionalGroup( + tag::ConditionalGroup::new(self.condition), + )))?; + f.write_fmt(Arguments::from(&self.content))?; + f.write_element(FormatElement::Tag(EndConditionalGroup)) + } +} + +impl std::fmt::Debug for ConditionalGroup<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ConditionalGroup") + .field("condition", &self.condition) + .field("content", &"{{content}}") + .finish() + } +} + /// IR element that forces the parent group to print in expanded mode. /// /// Has no effect if used outside of a group or element that introduce implicit groups (fill element). @@ -1841,6 +1970,91 @@ impl std::fmt::Debug for IndentIfGroupBreaks<'_, Context> { } } +/// Changes the definition of *fits* for `content`. Instead of measuring it in *flat*, measure it with +/// all line breaks expanded and test if no line exceeds the line width. The [`FitsExpanded`] acts +/// as a expands boundary similar to best fitting, meaning that a [hard_line_break] will not cause the parent group to expand. +/// +/// Useful in conjunction with a group with a condition. +/// +/// ## Examples +/// The outer group with the binary expression remains *flat* regardless of the array expression +/// that spans multiple lines. +/// +/// ``` +/// # use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; +/// # use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let content = format_with(|f| { +/// let group_id = f.group_id("header"); +/// +/// write!(f, [ +/// group(&format_args![ +/// text("a"), +/// soft_line_break_or_space(), +/// text("+"), +/// space(), +/// fits_expanded(&group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("a,"), space(), text("# comment"), expand_parent(), soft_line_break_or_space(), +/// text("b") +/// ]), +/// text("]") +/// ])) +/// ]), +/// ]) +/// }); +/// +/// let formatted = format!(SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(16).unwrap(), +/// ..SimpleFormatOptions::default() +/// }), +/// [content] +/// )?; +/// +/// assert_eq!( +/// "a + [\n\ta, # comment\n\tb\n]", +/// formatted.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +pub fn fits_expanded(content: &Content) -> FitsExpanded +where + Content: Format, +{ + FitsExpanded { + content: Argument::new(content), + condition: None, + } +} + +#[derive(Clone)] +pub struct FitsExpanded<'a, Context> { + content: Argument<'a, Context>, + condition: Option, +} + +impl FitsExpanded<'_, Context> { + /// Sets a `condition` to when the content should fit in expanded mode. The content uses the regular fits + /// definition if the `condition` is not met. + pub fn with_condition(mut self, condition: Option) -> Self { + self.condition = condition; + self + } +} + +impl Format for FitsExpanded<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartFitsExpanded( + tag::FitsExpanded::new().with_condition(self.condition), + )))?; + f.write_fmt(Arguments::from(&self.content))?; + f.write_element(FormatElement::Tag(EndFitsExpanded)) + } +} + /// Utility for formatting some content with an inline lambda function. #[derive(Copy, Clone)] pub struct FormatWith { diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index 68baa0558edea..1f031f0e1d39f 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -1,5 +1,5 @@ use super::tag::Tag; -use crate::format_element::tag::DedentMode; +use crate::format_element::tag::{Condition, DedentMode}; use crate::prelude::tag::GroupMode; use crate::prelude::*; use crate::printer::LineEnding; @@ -33,12 +33,17 @@ impl Document { #[derive(Debug)] enum Enclosing<'a> { Group(&'a tag::Group), + ConditionalGroup(&'a tag::ConditionalGroup), + FitsExpanded(&'a tag::FitsExpanded), BestFitting, } fn expand_parent(enclosing: &[Enclosing]) { - if let Some(Enclosing::Group(group)) = enclosing.last() { - group.propagate_expand(); + match enclosing.last() { + Some(Enclosing::Group(group)) => group.propagate_expand(), + Some(Enclosing::ConditionalGroup(group)) => group.propagate_expand(), + Some(Enclosing::FitsExpanded(fits_expanded)) => fits_expanded.propagate_expand(), + _ => {} } } @@ -58,6 +63,14 @@ impl Document { Some(Enclosing::Group(group)) => !group.mode().is_flat(), _ => false, }, + FormatElement::Tag(Tag::StartConditionalGroup(group)) => { + enclosing.push(Enclosing::ConditionalGroup(group)); + false + } + FormatElement::Tag(Tag::EndConditionalGroup) => match enclosing.pop() { + Some(Enclosing::ConditionalGroup(group)) => !group.mode().is_flat(), + _ => false, + }, FormatElement::Interned(interned) => match checked_interned.get(interned) { Some(interned_expands) => *interned_expands, None => { @@ -79,6 +92,16 @@ impl Document { enclosing.pop(); continue; } + FormatElement::Tag(Tag::StartFitsExpanded(fits_expanded)) => { + enclosing.push(Enclosing::FitsExpanded(fits_expanded)); + false + } + FormatElement::Tag(Tag::EndFitsExpanded) => { + enclosing.pop(); + // Fits expanded acts as a boundary + expands = false; + continue; + } FormatElement::StaticText { text } => text.contains('\n'), FormatElement::DynamicText { text, .. } => text.contains('\n'), FormatElement::SourceCodeSlice { @@ -441,6 +464,29 @@ impl Format> for &[FormatElement] { } } + StartConditionalGroup(group) => { + write!( + f, + [ + text("conditional_group(condition:"), + space(), + group.condition(), + text(","), + space() + ] + )?; + + match group.mode() { + GroupMode::Flat => {} + GroupMode::Expand => { + write!(f, [text("expand: true,"), space()])?; + } + GroupMode::Propagated => { + write!(f, [text("expand: propagated,"), space()])?; + } + } + } + StartIndentIfGroupBreaks(id) => { write!( f, @@ -491,6 +537,28 @@ impl Format> for &[FormatElement] { write!(f, [text("fill(")])?; } + StartFitsExpanded(tag::FitsExpanded { + condition, + propagate_expand, + }) => { + write!(f, [text("fits_expanded(propagate_expand:"), space()])?; + + if propagate_expand.get() { + write!(f, [text("true")])?; + } else { + write!(f, [text("false")])?; + } + + write!(f, [text(","), space()])?; + + if let Some(condition) = condition { + write!( + f, + [text("condition:"), space(), condition, text(","), space()] + )?; + } + } + StartEntry => { // handled after the match for all start tags } @@ -503,8 +571,10 @@ impl Format> for &[FormatElement] { | EndAlign | EndIndent | EndGroup + | EndConditionalGroup | EndLineSuffix | EndDedent + | EndFitsExpanded | EndVerbatim => { write!(f, [ContentArrayEnd, text(")")])?; } @@ -658,6 +728,31 @@ impl FormatElements for [FormatElement] { } } +impl Format> for Condition { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match (self.mode, self.group_id) { + (PrintMode::Flat, None) => write!(f, [text("if_fits_on_line")]), + (PrintMode::Flat, Some(id)) => write!( + f, + [ + text("if_group_fits_on_line("), + dynamic_text(&std::format!("\"{id:?}\""), None), + text(")") + ] + ), + (PrintMode::Expanded, None) => write!(f, [text("if_breaks")]), + (PrintMode::Expanded, Some(id)) => write!( + f, + [ + text("if_group_breaks("), + dynamic_text(&std::format!("\"{id:?}\""), None), + text(")") + ] + ), + } + } +} + #[cfg(test)] mod tests { use crate::prelude::*; diff --git a/crates/ruff_formatter/src/format_element/tag.rs b/crates/ruff_formatter/src/format_element/tag.rs index a443f909f0f8c..f586cc8b1c9c6 100644 --- a/crates/ruff_formatter/src/format_element/tag.rs +++ b/crates/ruff_formatter/src/format_element/tag.rs @@ -33,6 +33,16 @@ pub enum Tag { StartGroup(Group), EndGroup, + /// Creates a logical group similar to [`Tag::StartGroup`] but only if the condition is met. + /// This is an optimized representation for (assuming the content should only be grouped if another group fits): + /// + /// ```text + /// if_group_breaks(content, other_group_id), + /// if_group_fits_on_line(group(&content), other_group_id) + /// ``` + StartConditionalGroup(ConditionalGroup), + EndConditionalGroup, + /// Allows to specify content that gets printed depending on whatever the enclosing group /// is printed on a single line or multiple lines. See [crate::builders::if_group_breaks] for examples. StartConditionalContent(Condition), @@ -67,6 +77,9 @@ pub enum Tag { /// See [crate::builders::labelled] for documentation. StartLabelled(LabelId), EndLabelled, + + StartFitsExpanded(FitsExpanded), + EndFitsExpanded, } impl Tag { @@ -77,7 +90,8 @@ impl Tag { Tag::StartIndent | Tag::StartAlign(_) | Tag::StartDedent(_) - | Tag::StartGroup { .. } + | Tag::StartGroup(_) + | Tag::StartConditionalGroup(_) | Tag::StartConditionalContent(_) | Tag::StartIndentIfGroupBreaks(_) | Tag::StartFill @@ -85,6 +99,7 @@ impl Tag { | Tag::StartLineSuffix | Tag::StartVerbatim(_) | Tag::StartLabelled(_) + | Tag::StartFitsExpanded(_) ) } @@ -101,6 +116,7 @@ impl Tag { StartAlign(_) | EndAlign => TagKind::Align, StartDedent(_) | EndDedent => TagKind::Dedent, StartGroup(_) | EndGroup => TagKind::Group, + StartConditionalGroup(_) | EndConditionalGroup => TagKind::ConditionalGroup, StartConditionalContent(_) | EndConditionalContent => TagKind::ConditionalContent, StartIndentIfGroupBreaks(_) | EndIndentIfGroupBreaks => TagKind::IndentIfGroupBreaks, StartFill | EndFill => TagKind::Fill, @@ -108,6 +124,7 @@ impl Tag { StartLineSuffix | EndLineSuffix => TagKind::LineSuffix, StartVerbatim(_) | EndVerbatim => TagKind::Verbatim, StartLabelled(_) | EndLabelled => TagKind::Labelled, + StartFitsExpanded { .. } | EndFitsExpanded => TagKind::FitsExpanded, } } } @@ -122,6 +139,7 @@ pub enum TagKind { Align, Dedent, Group, + ConditionalGroup, ConditionalContent, IndentIfGroupBreaks, Fill, @@ -129,6 +147,7 @@ pub enum TagKind { LineSuffix, Verbatim, Labelled, + FitsExpanded, } #[derive(Debug, Copy, Default, Clone, Eq, PartialEq)] @@ -150,6 +169,27 @@ impl GroupMode { } } +#[derive(Debug, Clone, Eq, PartialEq, Default)] +pub struct FitsExpanded { + pub(crate) condition: Option, + pub(crate) propagate_expand: Cell, +} + +impl FitsExpanded { + pub fn new() -> Self { + Self::default() + } + + pub fn with_condition(mut self, condition: Option) -> Self { + self.condition = condition; + self + } + + pub fn propagate_expand(&self) { + self.propagate_expand.set(true) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct Group { id: Option, @@ -189,6 +229,33 @@ impl Group { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ConditionalGroup { + mode: Cell, + condition: Condition, +} + +impl ConditionalGroup { + pub fn new(condition: Condition) -> Self { + Self { + mode: Cell::new(GroupMode::Flat), + condition, + } + } + + pub fn condition(&self) -> Condition { + self.condition + } + + pub fn propagate_expand(&self) { + self.mode.set(GroupMode::Propagated) + } + + pub fn mode(&self) -> GroupMode { + self.mode.get() + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum DedentMode { /// Reduces the indent by a level (if the current indent is > 0) @@ -198,7 +265,7 @@ pub enum DedentMode { Root, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Condition { /// - `Flat` -> Omitted if the enclosing group is a multiline group, printed for groups fitting on a single line /// - `Expanded` -> Omitted if the enclosing group fits on a single line, printed if the group breaks over multiple lines. @@ -217,6 +284,34 @@ impl Condition { } } + pub fn if_fits_on_line() -> Self { + Self { + mode: PrintMode::Flat, + group_id: None, + } + } + + pub fn if_group_fits_on_line(group_id: GroupId) -> Self { + Self { + mode: PrintMode::Flat, + group_id: Some(group_id), + } + } + + pub fn if_breaks() -> Self { + Self { + mode: PrintMode::Expanded, + group_id: None, + } + } + + pub fn if_group_breaks(group_id: GroupId) -> Self { + Self { + mode: PrintMode::Expanded, + group_id: Some(group_id), + } + } + pub fn with_group_id(mut self, id: Option) -> Self { self.group_id = id; self diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 25687b7631f7b..a58514c10271d 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -7,6 +7,7 @@ mod stack; use crate::format_element::document::Document; use crate::format_element::tag::{Condition, GroupMode}; use crate::format_element::{BestFittingMode, BestFittingVariants, LineMode, PrintMode}; +use crate::prelude::tag; use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind}; use crate::printer::call_stack::{ CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame, @@ -143,40 +144,26 @@ impl<'a> Printer<'a> { } FormatElement::Tag(StartGroup(group)) => { - let group_mode = match group.mode() { - GroupMode::Expand | GroupMode::Propagated => PrintMode::Expanded, - GroupMode::Flat => { - match args.mode() { - PrintMode::Flat if self.state.measured_group_fits => { - // A parent group has already verified that this group fits on a single line - // Thus, just continue in flat mode - PrintMode::Flat - } - // The printer is either in expanded mode or it's necessary to re-measure if the group fits - // because the printer printed a line break - _ => { - self.state.measured_group_fits = true; - - // Measure to see if the group fits up on a single line. If that's the case, - // print the group in "flat" mode, otherwise continue in expanded mode - stack.push(TagKind::Group, args.with_print_mode(PrintMode::Flat)); - let fits = self.fits(queue, stack)?; - stack.pop(TagKind::Group)?; - - if fits { - PrintMode::Flat - } else { - PrintMode::Expanded - } - } - } - } - }; - - stack.push(TagKind::Group, args.with_print_mode(group_mode)); + let print_mode = + self.print_group(TagKind::Group, group.mode(), args, queue, stack)?; if let Some(id) = group.id() { - self.state.group_modes.insert_print_mode(id, group_mode); + self.state.group_modes.insert_print_mode(id, print_mode); + } + } + + FormatElement::Tag(StartConditionalGroup(group)) => { + let condition = group.condition(); + let expected_mode = match condition.group_id { + None => args.mode(), + Some(id) => self.state.group_modes.unwrap_print_mode(id, element), + }; + + if expected_mode == condition.mode { + self.print_group(TagKind::ConditionalGroup, group.mode(), args, queue, stack)?; + } else { + // Condition isn't met, render as normal content + stack.push(TagKind::ConditionalGroup, args); } } @@ -244,6 +231,29 @@ impl<'a> Printer<'a> { stack.push(TagKind::Verbatim, args); } + FormatElement::Tag(StartFitsExpanded(tag::FitsExpanded { condition, .. })) => { + let condition_met = match condition { + Some(condition) => { + let group_mode = match condition.group_id { + Some(group_id) => { + self.state.group_modes.unwrap_print_mode(group_id, element) + } + None => args.mode(), + }; + + condition.mode == group_mode + } + None => true, + }; + + if condition_met { + // We measured the inner groups all in expanded. It now is necessary to measure if the inner groups fit as well. + self.state.measured_group_fits = false; + } + + stack.push(TagKind::FitsExpanded, args); + } + FormatElement::Tag(tag @ (StartLabelled(_) | StartEntry)) => { stack.push(tag.kind(), args); } @@ -252,11 +262,13 @@ impl<'a> Printer<'a> { tag @ (EndLabelled | EndEntry | EndGroup + | EndConditionalGroup | EndIndent | EndDedent | EndAlign | EndConditionalContent | EndIndentIfGroupBreaks + | EndFitsExpanded | EndVerbatim | EndLineSuffix | EndFill), @@ -275,6 +287,49 @@ impl<'a> Printer<'a> { result } + fn print_group( + &mut self, + kind: TagKind, + mode: GroupMode, + args: PrintElementArgs, + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, + ) -> PrintResult { + let group_mode = match mode { + GroupMode::Expand | GroupMode::Propagated => PrintMode::Expanded, + GroupMode::Flat => { + match args.mode() { + PrintMode::Flat if self.state.measured_group_fits => { + // A parent group has already verified that this group fits on a single line + // Thus, just continue in flat mode + PrintMode::Flat + } + // The printer is either in expanded mode or it's necessary to re-measure if the group fits + // because the printer printed a line break + _ => { + self.state.measured_group_fits = true; + + // Measure to see if the group fits up on a single line. If that's the case, + // print the group in "flat" mode, otherwise continue in expanded mode + stack.push(kind, args.with_print_mode(PrintMode::Flat)); + let fits = self.fits(queue, stack)?; + stack.pop(kind)?; + + if fits { + PrintMode::Flat + } else { + PrintMode::Expanded + } + } + } + } + }; + + stack.push(kind, args.with_print_mode(group_mode)); + + Ok(group_mode) + } + fn print_text(&mut self, text: &str, source_range: Option) { if !self.state.pending_indent.is_empty() { let (indent_char, repeat_count) = match self.options.indent_style() { @@ -1050,22 +1105,24 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { } FormatElement::Tag(StartGroup(group)) => { - if self.must_be_flat && !group.mode().is_flat() { - return Ok(Fits::No); - } + return self.fits_group(TagKind::Group, group.mode(), group.id(), args); + } - // Continue printing groups in expanded mode if measuring a `fits_expanded` element - let print_mode = if !group.mode().is_flat() { - PrintMode::Expanded - } else { - args.mode() - }; + FormatElement::Tag(StartConditionalGroup(group)) => { + let condition = group.condition(); - self.stack - .push(TagKind::Group, args.with_print_mode(print_mode)); + let print_mode = match condition.group_id { + None => args.mode(), + Some(group_id) => self + .group_modes() + .get_print_mode(group_id) + .unwrap_or_else(|| args.mode()), + }; - if let Some(id) = group.id() { - self.group_modes_mut().insert_print_mode(id, print_mode); + if condition.mode == print_mode { + return self.fits_group(TagKind::ConditionalGroup, group.mode(), None, args); + } else { + self.stack.push(TagKind::ConditionalGroup, args); } } @@ -1113,6 +1170,42 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { return invalid_end_tag(TagKind::LineSuffix, self.stack.top_kind()); } + FormatElement::Tag(StartFitsExpanded(tag::FitsExpanded { + condition, + propagate_expand, + })) => { + let condition_met = match condition { + Some(condition) => { + let group_mode = match condition.group_id { + Some(group_id) => self + .group_modes() + .get_print_mode(group_id) + .unwrap_or_else(|| args.mode()), + None => args.mode(), + }; + + condition.mode == group_mode + } + None => true, + }; + + if condition_met { + // Measure in fully expanded mode. + self.stack.push( + TagKind::FitsExpanded, + args.with_print_mode(PrintMode::Expanded) + .with_measure_mode(MeasureMode::AllLines), + ) + } else { + if propagate_expand.get() && args.mode().is_flat() { + return Ok(Fits::No); + } + + // As usual + self.stack.push(TagKind::FitsExpanded, args) + } + } + FormatElement::Tag( tag @ (StartFill | StartVerbatim(_) | StartLabelled(_) | StartEntry), ) => { @@ -1125,11 +1218,13 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { | EndLabelled | EndEntry | EndGroup + | EndConditionalGroup | EndIndentIfGroupBreaks | EndConditionalContent | EndAlign | EndDedent - | EndIndent), + | EndIndent + | EndFitsExpanded), ) => { self.stack.pop(tag.kind())?; } @@ -1138,6 +1233,34 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { Ok(Fits::Maybe) } + fn fits_group( + &mut self, + kind: TagKind, + mode: GroupMode, + id: Option, + args: PrintElementArgs, + ) -> PrintResult { + if self.must_be_flat && !mode.is_flat() { + return Ok(Fits::No); + } + + // Continue printing groups in expanded mode if measuring a `best_fitting` element where + // a group expands. + let print_mode = if !mode.is_flat() { + PrintMode::Expanded + } else { + args.mode() + }; + + self.stack.push(kind, args.with_print_mode(print_mode)); + + if let Some(id) = id { + self.group_modes_mut().insert_print_mode(id, print_mode); + } + + Ok(Fits::Maybe) + } + fn fits_text(&mut self, text: &str) -> Fits { let indent = std::mem::take(&mut self.state.pending_indent); self.state.line_width += indent.level() as usize * self.options().indent_width() as usize diff --git a/crates/ruff_python_formatter/src/comments/debug.rs b/crates/ruff_python_formatter/src/comments/debug.rs index c166f4c561e7d..37be2ed9c0011 100644 --- a/crates/ruff_python_formatter/src/comments/debug.rs +++ b/crates/ruff_python_formatter/src/comments/debug.rs @@ -29,10 +29,8 @@ impl Debug for DebugComment<'_> { strut .field("text", &self.comment.slice.text(self.source_code)) - .field("position", &self.comment.line_position); - - #[cfg(debug_assertions)] - strut.field("formatted", &self.comment.formatted.get()); + .field("position", &self.comment.line_position) + .field("formatted", &self.comment.formatted.get()); strut.finish() }