Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions library/core/src/fmt/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ pub(super) fn debug_struct_new<'a, 'b>(
DebugStruct { fmt, result, has_fields: false }
}

fn debug_struct_field_non_pretty<F>(
fmt: &mut fmt::Formatter<'_>,
prefix: &str,
name: &str,
value_fmt: F,
) -> fmt::Result
where
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
{
fmt.write_str(prefix)?;
fmt.write_str(name)?;
fmt.write_str(": ")?;
value_fmt(fmt)
}

impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
/// Adds a new field to the generated struct output.
///
Expand Down Expand Up @@ -156,17 +171,29 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
writer.write_str(",\n")
} else {
let prefix = if self.has_fields { ", " } else { " { " };
self.fmt.write_str(prefix)?;
self.fmt.write_str(name)?;
self.fmt.write_str(": ")?;
value_fmt(self.fmt)
debug_struct_field_non_pretty(self.fmt, prefix, name, value_fmt)
}
});

self.has_fields = true;
self
}

/// Adds a new field to the generated struct output in non-pretty mode.
///
/// Fast path version of [`Self::field`] that allows skipping the pretty mode check.
/// It's the caller's responsibility to verify non-pretty mode is configured
/// prior to calling this function.
pub(super) fn field_non_pretty(&mut self, name: &str, value: &dyn fmt::Debug) -> &mut Self {
self.result = self.result.and_then(|_| {
let prefix = if self.has_fields { ", " } else { " { " };
debug_struct_field_non_pretty(self.fmt, prefix, name, |f| value.fmt(f))
});

self.has_fields = true;
self
}

/// Marks the struct as non-exhaustive, indicating to the reader that there are some other
/// fields that are not shown in the debug representation.
///
Expand Down Expand Up @@ -250,6 +277,18 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
self.result
}

/// Finishes output in non-pretty mode and returns any error encountered.
///
/// Fast path version of [`Self::finish`] that allows skipping the pretty mode check.
/// It's the caller's responsibility to verify non-pretty mode is configured
/// prior to calling this function.
pub(super) fn finish_non_pretty(&mut self) -> fmt::Result {
if self.has_fields {
self.result = self.result.and_then(|_| self.fmt.write_str(" }"));
}
self.result
}

fn is_pretty(&self) -> bool {
self.fmt.alternate()
}
Expand Down
66 changes: 41 additions & 25 deletions library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,27 @@ pub struct Formatter<'a> {
buf: &'a mut (dyn Write + 'a),
}

// The fixed-arity derive helpers check `$formatter.alternate()` ("pretty mode") once
// so that in the common case (non-alternate mode) we can use the fast path
// that does not require re-checking it once for every field.
macro_rules! debug_struct_fixed_fields {
(
$formatter:ident,
$name:expr,
$(($field_name:expr, $value:expr)),+ $(,)?
) => {{
if $formatter.alternate() {
let mut builder = builders::debug_struct_new($formatter, $name);
$(builder.field($field_name, $value);)+
builder.finish()
} else {
let mut builder = builders::debug_struct_new($formatter, $name);
$(builder.field_non_pretty($field_name, $value);)+
builder.finish_non_pretty()
}
}};
}

impl<'a> Formatter<'a> {
/// Creates a new formatter with given [`FormattingOptions`].
///
Expand Down Expand Up @@ -2460,9 +2481,7 @@ impl<'a> Formatter<'a> {
name1: &str,
value1: &dyn Debug,
) -> Result {
let mut builder = builders::debug_struct_new(self, name);
builder.field(name1, value1);
builder.finish()
debug_struct_fixed_fields!(self, name, (name1, value1))
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller
Expand All @@ -2478,10 +2497,7 @@ impl<'a> Formatter<'a> {
name2: &str,
value2: &dyn Debug,
) -> Result {
let mut builder = builders::debug_struct_new(self, name);
builder.field(name1, value1);
builder.field(name2, value2);
builder.finish()
debug_struct_fixed_fields!(self, name, (name1, value1), (name2, value2))
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller
Expand All @@ -2499,11 +2515,7 @@ impl<'a> Formatter<'a> {
name3: &str,
value3: &dyn Debug,
) -> Result {
let mut builder = builders::debug_struct_new(self, name);
builder.field(name1, value1);
builder.field(name2, value2);
builder.field(name3, value3);
builder.finish()
debug_struct_fixed_fields!(self, name, (name1, value1), (name2, value2), (name3, value3))
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller
Expand All @@ -2523,12 +2535,14 @@ impl<'a> Formatter<'a> {
name4: &str,
value4: &dyn Debug,
) -> Result {
let mut builder = builders::debug_struct_new(self, name);
builder.field(name1, value1);
builder.field(name2, value2);
builder.field(name3, value3);
builder.field(name4, value4);
builder.finish()
debug_struct_fixed_fields!(
self,
name,
(name1, value1),
(name2, value2),
(name3, value3),
(name4, value4),
)
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller
Expand All @@ -2550,13 +2564,15 @@ impl<'a> Formatter<'a> {
name5: &str,
value5: &dyn Debug,
) -> Result {
let mut builder = builders::debug_struct_new(self, name);
builder.field(name1, value1);
builder.field(name2, value2);
builder.field(name3, value3);
builder.field(name4, value4);
builder.field(name5, value5);
builder.finish()
debug_struct_fixed_fields!(
self,
name,
(name1, value1),
(name2, value2),
(name3, value3),
(name4, value4),
(name5, value5),
)
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller binaries.
Expand Down
36 changes: 36 additions & 0 deletions library/coretests/tests/fmt/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,42 @@ mod debug_struct {

assert_eq!("Foo {\n bar: true,\n baz: false,\n}", format!("{Foo:#?}"))
}

#[test]
fn test_derived_helpers_match_builder() {
fn assert_matches_builder(builder: &dyn fmt::Debug, derived: &dyn fmt::Debug) {
assert_eq!(format!("{builder:?}"), format!("{derived:?}"));
assert_eq!(format!("{builder:#?}"), format!("{derived:#?}"));
}

macro_rules! check_arity {
($name:ident { $($field:ident: $value:expr),+ $(,)? }) => {{
#[derive(Debug)]
struct $name {
$($field: u8),+
}

struct ViaBuilder<'a>(&'a $name);

impl fmt::Debug for ViaBuilder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($name))
$(.field(stringify!($field), &self.0.$field))+
.finish()
}
}

let value = $name { $($field: $value),+ };
assert_matches_builder(&ViaBuilder(&value), &value);
}};
}

check_arity!(One { a: 1 });
check_arity!(Two { a: 1, b: 2 });
check_arity!(Three { a: 1, b: 2, c: 3 });
check_arity!(Four { a: 1, b: 2, c: 3, d: 4 });
check_arity!(Five { a: 1, b: 2, c: 3, d: 4, e: 5 });
}
}

mod debug_tuple {
Expand Down
Loading