From e98ea52762e7ecf492b3a1880deb78610ef22996 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Wed, 1 May 2019 17:19:41 +1000 Subject: [PATCH 1/2] add key and value methods to DebugMap --- .../library-features/debug-map-key-value.md | 9 + src/libcore/fmt/builders.rs | 158 ++++++++++++++++-- src/libcore/tests/fmt/builders.rs | 93 ++++++++++- src/libcore/tests/lib.rs | 1 + 4 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 src/doc/unstable-book/src/library-features/debug-map-key-value.md diff --git a/src/doc/unstable-book/src/library-features/debug-map-key-value.md b/src/doc/unstable-book/src/library-features/debug-map-key-value.md new file mode 100644 index 00000000000..ae839bf2ac3 --- /dev/null +++ b/src/doc/unstable-book/src/library-features/debug-map-key-value.md @@ -0,0 +1,9 @@ +# `debug_map_key_value` + +The tracking issue for this feature is: [#62482] + +[#62482]: https://github.com/rust-lang/rust/issues/62482 + +------------------------ + +Add the methods `key` and `value` to `DebugMap` so that an entry can be formatted across multiple calls without additional buffering. diff --git a/src/libcore/fmt/builders.rs b/src/libcore/fmt/builders.rs index df86da5fc39..abc97aacbb9 100644 --- a/src/libcore/fmt/builders.rs +++ b/src/libcore/fmt/builders.rs @@ -1,37 +1,50 @@ use crate::fmt; -struct PadAdapter<'a> { - buf: &'a mut (dyn fmt::Write + 'a), +struct PadAdapter<'buf, 'state> { + buf: &'buf mut (dyn fmt::Write + 'buf), + state: &'state mut PadAdapterState, +} + +struct PadAdapterState { on_newline: bool, } -impl<'a> PadAdapter<'a> { - fn wrap<'b, 'c: 'a+'b>(fmt: &'c mut fmt::Formatter<'_>, slot: &'b mut Option) - -> fmt::Formatter<'b> { +impl Default for PadAdapterState { + fn default() -> Self { + PadAdapterState { + on_newline: true, + } + } +} + +impl<'buf, 'state> PadAdapter<'buf, 'state> { + fn wrap<'slot, 'fmt: 'buf+'slot>(fmt: &'fmt mut fmt::Formatter<'_>, + slot: &'slot mut Option, + state: &'state mut PadAdapterState) -> fmt::Formatter<'slot> { fmt.wrap_buf(move |buf| { *slot = Some(PadAdapter { buf, - on_newline: true, + state, }); slot.as_mut().unwrap() }) } } -impl fmt::Write for PadAdapter<'_> { +impl fmt::Write for PadAdapter<'_, '_> { fn write_str(&mut self, mut s: &str) -> fmt::Result { while !s.is_empty() { - if self.on_newline { + if self.state.on_newline { self.buf.write_str(" ")?; } let split = match s.find('\n') { Some(pos) => { - self.on_newline = true; + self.state.on_newline = true; pos + 1 } None => { - self.on_newline = false; + self.state.on_newline = false; s.len() } }; @@ -133,7 +146,8 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> { self.fmt.write_str(" {\n")?; } let mut slot = None; - let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot); + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot, &mut state); writer.write_str(name)?; writer.write_str(": ")?; value.fmt(&mut writer)?; @@ -279,7 +293,8 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> { self.fmt.write_str("(\n")?; } let mut slot = None; - let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot); + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot, &mut state); value.fmt(&mut writer)?; writer.write_str(",\n") } else { @@ -349,7 +364,8 @@ impl<'a, 'b: 'a> DebugInner<'a, 'b> { self.fmt.write_str("\n")?; } let mut slot = None; - let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot); + let mut state = Default::default(); + let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot, &mut state); entry.fmt(&mut writer)?; writer.write_str(",\n") } else { @@ -676,6 +692,9 @@ pub struct DebugMap<'a, 'b: 'a> { fmt: &'a mut fmt::Formatter<'b>, result: fmt::Result, has_fields: bool, + has_key: bool, + // The state of newlines is tracked between keys and values + state: PadAdapterState, } pub fn debug_map_new<'a, 'b>(fmt: &'a mut fmt::Formatter<'b>) -> DebugMap<'a, 'b> { @@ -684,6 +703,8 @@ pub fn debug_map_new<'a, 'b>(fmt: &'a mut fmt::Formatter<'b>) -> DebugMap<'a, 'b fmt, result, has_fields: false, + has_key: false, + state: Default::default(), } } @@ -712,25 +733,121 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] pub fn entry(&mut self, key: &dyn fmt::Debug, value: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { + self.key(key).value(value) + } + + /// Adds the key part of a new entry to the map output. + /// + /// This method, together with `value`, is an alternative to `entry` that + /// can be used when the complete entry isn't known upfront. Prefer the `entry` + /// method when it's possible to use. + /// + /// # Panics + /// + /// `key` must be called before `value` and each call to `key` must be followed + /// by a corresponding call to `value`. Otherwise this method will panic. + /// + /// # Examples + /// + /// ``` + /// use std::fmt; + /// + /// struct Foo(Vec<(String, i32)>); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt.debug_map() + /// .key(&"whole").value(&self.0) // We add the "whole" entry. + /// .finish() + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), + /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// ); + /// ``` + #[unstable(feature = "debug_map_key_value", + reason = "recently added", + issue = "62482")] + pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { + assert!(!self.has_key, "attempted to begin a new map entry \ + without completing the previous one"); + self.result = self.result.and_then(|_| { if self.is_pretty() { if !self.has_fields { self.fmt.write_str("\n")?; } let mut slot = None; - let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot); + self.state = Default::default(); + let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot, &mut self.state); key.fmt(&mut writer)?; writer.write_str(": ")?; - value.fmt(&mut writer)?; - writer.write_str(",\n") } else { if self.has_fields { self.fmt.write_str(", ")? } key.fmt(self.fmt)?; self.fmt.write_str(": ")?; - value.fmt(self.fmt) } + + self.has_key = true; + Ok(()) + }); + + self + } + + /// Adds the value part of a new entry to the map output. + /// + /// This method, together with `key`, is an alternative to `entry` that + /// can be used when the complete entry isn't known upfront. Prefer the `entry` + /// method when it's possible to use. + /// + /// # Panics + /// + /// `key` must be called before `value` and each call to `key` must be followed + /// by a corresponding call to `value`. Otherwise this method will panic. + /// + /// # Examples + /// + /// ``` + /// use std::fmt; + /// + /// struct Foo(Vec<(String, i32)>); + /// + /// impl fmt::Debug for Foo { + /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt.debug_map() + /// .key(&"whole").value(&self.0) // We add the "whole" entry. + /// .finish() + /// } + /// } + /// + /// assert_eq!( + /// format!("{:?}", Foo(vec![("A".to_string(), 10), ("B".to_string(), 11)])), + /// "{\"whole\": [(\"A\", 10), (\"B\", 11)]}", + /// ); + /// ``` + #[unstable(feature = "debug_map_key_value", + reason = "recently added", + issue = "62482")] + pub fn value(&mut self, value: &dyn fmt::Debug) -> &mut DebugMap<'a, 'b> { + assert!(self.has_key, "attempted to format a map value before its key"); + + self.result = self.result.and_then(|_| { + if self.is_pretty() { + let mut slot = None; + let mut writer = PadAdapter::wrap(&mut self.fmt, &mut slot, &mut self.state); + value.fmt(&mut writer)?; + writer.write_str(",\n")?; + } else { + value.fmt(self.fmt)?; + } + + self.has_key = false; + Ok(()) }); self.has_fields = true; @@ -775,6 +892,11 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// Finishes output and returns any error encountered. /// + /// # Panics + /// + /// `key` must be called before `value` and each call to `key` must be followed + /// by a corresponding call to `value`. Otherwise this method will panic. + /// /// # Examples /// /// ``` @@ -797,6 +919,8 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// ``` #[stable(feature = "debug_builders", since = "1.2.0")] pub fn finish(&mut self) -> fmt::Result { + assert!(!self.has_key, "attempted to finish a map with a partial entry"); + self.result.and_then(|_| self.fmt.write_str("}")) } diff --git a/src/libcore/tests/fmt/builders.rs b/src/libcore/tests/fmt/builders.rs index 62fe09c5eb3..200659b91bb 100644 --- a/src/libcore/tests/fmt/builders.rs +++ b/src/libcore/tests/fmt/builders.rs @@ -211,9 +211,9 @@ mod debug_map { #[test] fn test_single() { - struct Foo; + struct Entry; - impl fmt::Debug for Foo { + impl fmt::Debug for Entry { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_map() .entry(&"bar", &true) @@ -221,19 +221,32 @@ mod debug_map { } } - assert_eq!("{\"bar\": true}", format!("{:?}", Foo)); + struct KeyValue; + + impl fmt::Debug for KeyValue { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar").value(&true) + .finish() + } + } + + assert_eq!(format!("{:?}", Entry), format!("{:?}", KeyValue)); + assert_eq!(format!("{:#?}", Entry), format!("{:#?}", KeyValue)); + + assert_eq!("{\"bar\": true}", format!("{:?}", Entry)); assert_eq!( "{ \"bar\": true, }", - format!("{:#?}", Foo)); + format!("{:#?}", Entry)); } #[test] fn test_multiple() { - struct Foo; + struct Entry; - impl fmt::Debug for Foo { + impl fmt::Debug for Entry { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_map() .entry(&"bar", &true) @@ -242,13 +255,27 @@ mod debug_map { } } - assert_eq!("{\"bar\": true, 10: 10/20}", format!("{:?}", Foo)); + struct KeyValue; + + impl fmt::Debug for KeyValue { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar").value(&true) + .key(&10).value(&format_args!("{}/{}", 10, 20)) + .finish() + } + } + + assert_eq!(format!("{:?}", Entry), format!("{:?}", KeyValue)); + assert_eq!(format!("{:#?}", Entry), format!("{:#?}", KeyValue)); + + assert_eq!("{\"bar\": true, 10: 10/20}", format!("{:?}", Entry)); assert_eq!( "{ \"bar\": true, 10: 10/20, }", - format!("{:#?}", Foo)); + format!("{:#?}", Entry)); } #[test] @@ -291,6 +318,56 @@ mod debug_map { }", format!("{:#?}", Bar)); } + + #[test] + #[should_panic] + fn test_invalid_key_when_entry_is_incomplete() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar") + .key(&"invalid") + .finish() + } + } + + format!("{:?}", Foo); + } + + #[test] + #[should_panic] + fn test_invalid_finish_incomplete_entry() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .key(&"bar") + .finish() + } + } + + format!("{:?}", Foo); + } + + #[test] + #[should_panic] + fn test_invalid_value_before_key() { + struct Foo; + + impl fmt::Debug for Foo { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map() + .value(&"invalid") + .key(&"bar") + .finish() + } + } + + format!("{:?}", Foo); + } } mod debug_set { diff --git a/src/libcore/tests/lib.rs b/src/libcore/tests/lib.rs index bf072a9243b..4b48d122590 100644 --- a/src/libcore/tests/lib.rs +++ b/src/libcore/tests/lib.rs @@ -3,6 +3,7 @@ #![feature(cell_update)] #![feature(core_private_bignum)] #![feature(core_private_diy_float)] +#![feature(debug_map_key_value)] #![feature(dec2flt)] #![feature(euclidean_division)] #![feature(exact_size_is_empty)] From 70d630fd5a57d436b6a5064840b69920b10a110c Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 9 Jul 2019 08:30:20 +1000 Subject: [PATCH 2/2] add feature to docs --- src/libcore/fmt/builders.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libcore/fmt/builders.rs b/src/libcore/fmt/builders.rs index abc97aacbb9..cb4e32622ff 100644 --- a/src/libcore/fmt/builders.rs +++ b/src/libcore/fmt/builders.rs @@ -750,6 +750,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// # Examples /// /// ``` + /// # #![feature(debug_map_key_value)] /// use std::fmt; /// /// struct Foo(Vec<(String, i32)>); @@ -813,6 +814,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// # Examples /// /// ``` + /// # #![feature(debug_map_key_value)] /// use std::fmt; /// /// struct Foo(Vec<(String, i32)>);