Skip to content

Commit

Permalink
Re-add format_extensions.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 15, 2023
1 parent e04c281 commit 6dc9fa3
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 12 deletions.
9 changes: 0 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions crates/ruff_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ publish = false
edition = "2021"

[dependencies]
cfg-if = { version = "1.0.0" }
countme = { version = "3.0.1" }
drop_bomb = { version = "0.1.5" }
indexmap = { version = "1.9.1" }
ruff_text_size = { path = "../ruff_text_size" }
rustc-hash = { workspace = true }
schemars = { version = "0.8.10", optional = true }
Expand Down
180 changes: 180 additions & 0 deletions crates/ruff_formatter/src/format_extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#![allow(dead_code)]

use crate::prelude::*;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::ops::Deref;

use crate::Buffer;

/// Utility trait that allows memorizing the output of a [Format].
/// Useful to avoid re-formatting the same object twice.
pub trait MemoizeFormat<Context> {
/// Returns a formattable object that memoizes the result of `Format` by cloning.
/// Mainly useful if the same sub-tree can appear twice in the formatted output because it's
/// used inside of `if_group_breaks` or `if_group_fits_single_line`.
///
/// ```
/// use std::cell::Cell;
/// use ruff_formatter::{format, write};
/// use ruff_formatter::prelude::*;
/// use ruff_text_size::TextSize;
///
/// struct MyFormat {
/// value: Cell<u64>
/// }
///
/// impl MyFormat {
/// pub fn new() -> Self {
/// Self { value: Cell::new(1) }
/// }
/// }
///
/// impl Format<SimpleFormatContext> for MyFormat {
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
/// let value = self.value.get();
/// self.value.set(value + 1);
///
/// write!(f, [dynamic_text(&std::format!("Formatted {value} times."), TextSize::from(0))])
/// }
/// }
///
/// # fn main() -> FormatResult<()> {
/// let normal = MyFormat::new();
///
/// // Calls `format` for everytime the object gets formatted
/// assert_eq!(
/// "Formatted 1 times. Formatted 2 times.",
/// format!(SimpleFormatContext::default(), [normal, space(), normal])?.print()?.as_code()
/// );
///
/// // Memoized memoizes the result and calls `format` only once.
/// let memoized = normal.memoized();
/// assert_eq!(
/// "Formatted 3 times. Formatted 3 times.",
/// format![SimpleFormatContext::default(), [memoized, space(), memoized]]?.print()?.as_code()
/// );
/// # Ok(())
/// # }
/// ```
///
fn memoized(self) -> Memoized<Self, Context>
where
Self: Sized + Format<Context>,
{
Memoized::new(self)
}
}

impl<T, Context> MemoizeFormat<Context> for T where T: Format<Context> {}

/// Memoizes the output of its inner [Format] to avoid re-formatting a potential expensive object.
#[derive(Debug)]
pub struct Memoized<F, Context> {
inner: F,
memory: RefCell<Option<FormatResult<Option<FormatElement>>>>,
options: PhantomData<Context>,
}

impl<F, Context> Memoized<F, Context>
where
F: Format<Context>,
{
fn new(inner: F) -> Self {
Self {
inner,
memory: RefCell::new(None),
options: PhantomData,
}
}

/// Gives access to the memoized content.
///
/// Performs the formatting if the content hasn't been formatted at this point.
///
/// # Example
///
/// Inspect if some memoized content breaks.
///
/// ```rust
/// use std::cell::Cell;
/// use ruff_formatter::{format, write};
/// use ruff_formatter::prelude::*;
/// use ruff_text_size::TextSize;
///
/// #[derive(Default)]
/// struct Counter {
/// value: Cell<u64>
/// }
///
/// impl Format<SimpleFormatContext> for Counter {
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
/// let current = self.value.get();
///
/// write!(f, [
/// text("Count:"),
/// space(),
/// dynamic_text(&std::format!("{current}"), TextSize::default()),
/// hard_line_break()
/// ])?;
///
/// self.value.set(current + 1);
/// Ok(())
/// }
/// }
///
/// # fn main() -> FormatResult<()> {
/// let content = format_with(|f| {
/// let mut counter = Counter::default().memoized();
/// let counter_content = counter.inspect(f)?;
///
/// if counter_content.will_break() {
/// write!(f, [text("Counter:"), block_indent(&counter)])
/// } else {
/// write!(f, [text("Counter:"), counter])
/// }?;
///
/// write!(f, [counter])
/// });
///
///
/// let formatted = format!(SimpleFormatContext::default(), [content])?;
/// assert_eq!("Counter:\n\tCount: 0\nCount: 0\n", formatted.print()?.as_code());
/// # Ok(())
/// # }
///
/// ```
pub fn inspect(&mut self, f: &mut Formatter<Context>) -> FormatResult<&[FormatElement]> {
let result = self
.memory
.get_mut()
.get_or_insert_with(|| f.intern(&self.inner));

match result.as_ref() {
Ok(Some(FormatElement::Interned(interned))) => Ok(interned.deref()),
Ok(Some(other)) => Ok(std::slice::from_ref(other)),
Ok(None) => Ok(&[]),
Err(error) => Err(*error),
}
}
}

impl<F, Context> Format<Context> for Memoized<F, Context>
where
F: Format<Context>,
{
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
let mut memory = self.memory.borrow_mut();
let result = memory.get_or_insert_with(|| f.intern(&self.inner));

match result {
Ok(Some(elements)) => {
f.write_element(elements.clone())?;

Ok(())
}
Ok(None) => Ok(()),
Err(err) => Err(*err),
}
}
}
1 change: 1 addition & 0 deletions crates/ruff_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod buffer;
mod builders;
pub mod diagnostics;
pub mod format_element;
mod format_extensions;
pub mod formatter;
pub mod group_id;
pub mod macros;
Expand Down

0 comments on commit 6dc9fa3

Please sign in to comment.