impl_more/
display.rs

1/// Implements [`Display`] for structs by forwarding to one of its field.
2///
3/// Emitted code is not compatible with `#[no_std]`.
4///
5/// Newtype structs can omit the field identifier.
6///
7/// # Examples
8///
9/// For newtype struct:
10///
11/// ```
12/// # use impl_more::forward_display;
13/// struct Foo(String);
14///
15/// impl_more::forward_display!(Foo);
16///
17/// assert_eq!(Foo("hello world".to_owned()).to_string(), "hello world");
18/// ```
19///
20/// For struct with named field:
21///
22/// ```
23/// # use impl_more::forward_display;
24/// struct Bar {
25///     inner: u64,
26/// }
27///
28/// impl_more::forward_display!(Bar => inner);
29///
30/// assert_eq!(Bar { inner: 42 }.to_string(), "42");
31/// ```
32///
33/// For generic newtype struct (note that `Display` bounds are applied to all type parameters):
34///
35/// ```
36/// # use impl_more::forward_display;
37/// struct Baz<T>(T);
38///
39/// impl_more::forward_display!(<T> in Baz<T>);
40///
41/// assert_eq!(Baz(42u64).to_string(), "42");
42/// ```
43///
44/// [`Display`]: std::fmt::Display
45#[macro_export]
46macro_rules! forward_display {
47    (<$($generic:ident),+> in $this:ty => $field:ident) => {
48        impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
49            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
50                ::core::fmt::Display::fmt(&self.$field, fmt)
51            }
52        }
53    };
54
55    (<$($generic:ident),+> in $this:ty) => {
56        impl <$($generic: ::core::fmt::Display),+> ::core::fmt::Display for $this {
57            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
58                ::core::fmt::Display::fmt(&self.0, fmt)
59            }
60        }
61    };
62
63    ($ty:ty) => {
64        impl ::core::fmt::Display for $ty {
65            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
66                ::core::fmt::Display::fmt(&self.0, fmt)
67            }
68        }
69    };
70
71    ($ty:ty => $field:ident) => {
72        impl ::core::fmt::Display for $ty {
73            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
74                ::core::fmt::Display::fmt(&self.$field, fmt)
75            }
76        }
77    };
78}
79
80/// Implements [`Display`] for structs using a `format!`-like string constructor.
81///
82/// # Examples
83///
84/// Display implementation can be just a string literal.
85///
86/// ```
87/// # use impl_more::forward_display;
88/// struct Hello;
89/// impl_more::impl_display!(Hello: "hello world");
90/// assert_eq!(Hello.to_string(), "hello world");
91/// ```
92///
93/// Explicit and inline format args are supported.
94///
95/// ```
96/// # use impl_more::forward_display;
97/// struct Hello2;
98/// impl_more::impl_display!(Hello2: "hello world {}", 2);
99/// assert_eq!(Hello2.to_string(), "hello world 2");
100///
101/// const HI: &str = "hello";
102///
103/// struct Hello3;
104/// impl_more::impl_display!(Hello3: "{HI} world");
105/// assert_eq!(Hello3.to_string(), "hello world");
106/// ```
107///
108/// [`Display`]: std::fmt::Display
109#[macro_export]
110macro_rules! impl_display {
111    // no format args
112    ($ty:ty: $format:literal) => {
113        impl ::core::fmt::Display for $ty {
114            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
115                ::core::write!(fmt, $format)
116            }
117        }
118    };
119
120    // with explicit format args
121    ($ty:ty: $format:literal, $($args:expr),+) => {
122        impl ::core::fmt::Display for $ty {
123            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
124                ::core::write!(fmt, $format, $($args),+)
125            }
126        }
127    };
128
129    // strip trailing comma and forward to format args branch
130    ($ty:ty: $format:literal, $($args:expr),+ ,) => {
131        $crate::impl_display!($ty: $format, $($args),+);
132    };
133}
134
135/// Implements [`Display`] for enums using a static string or format args for each variant.
136///
137/// # Examples
138///
139/// ```
140/// # extern crate alloc;
141/// use impl_more::impl_display_enum;
142///
143/// enum Foo {
144///     Bar,
145///     Qux,
146/// }
147///
148/// impl_display_enum!(Foo: Bar => "bar", Qux => "qux");
149///
150/// assert_eq!(Foo::Bar.to_string(), "bar");
151/// assert_eq!(Foo::Qux.to_string(), "qux");
152///
153/// enum CoordOrMsg {
154///     Coord(i64, i64),
155///     Msg(&'static str),
156/// }
157///
158/// impl_display_enum!(CoordOrMsg: Coord(x, y) => "{x}, {y}", Msg(msg) => "message: {msg}");
159///
160/// assert_eq!(CoordOrMsg::Coord(4, 2).to_string(), "4, 2");
161/// assert_eq!(CoordOrMsg::Msg("hi").to_string(), "message: hi");
162/// ```
163///
164/// [`Display`]: std::fmt::Display
165#[macro_export]
166macro_rules! impl_display_enum {
167    ($ty:ty: $($variant:ident => $stringified:literal),+) => {
168        impl ::core::fmt::Display for $ty {
169            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
170                fmt.write_str(match self {
171                    $(
172                        Self::$variant => $stringified,
173                    )*
174                })
175            }
176        }
177    };
178
179    ($ty:ty: $($variant:ident => $stringified:literal),+ ,) => {
180        $crate::impl_display_enum!($ty: $($variant => $stringified),+);
181    };
182
183    ($ty:ty: $($variant:ident ($($inner:tt),+) => $format:literal),+) => {
184        impl ::core::fmt::Display for $ty {
185            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
186                use ::core::fmt::Write as _;
187
188                // a more efficient method (format_args) is blocked by:
189                // https://github.com/rust-lang/rust/issues/15023
190                let mut buf = ::std::string::String::new();
191
192                match self {
193                    $(
194                        Self::$variant($($crate::impl_display_enum!(iou @ $inner)),+) =>
195                            ::core::write!(&mut buf, $format)?,
196                    )*
197                };
198
199                fmt.write_str(&buf)
200            }
201        }
202    };
203
204    ($ty:ty: $($variant:ident ($($inner:tt),+) => $format:literal),+ ,) => {
205        $crate::impl_display_enum!($ty: $($variant ($($inner),+) => $format),+);
206    };
207
208    ($ty:ty: $($variant:ident { $($inner:ident),+ } => $format:literal),+) => {
209        impl ::core::fmt::Display for $ty {
210            fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
211                use ::core::fmt::Write as _;
212
213                // a more efficient method (format_args) is blocked by:
214                // https://github.com/rust-lang/rust/issues/15023
215                let mut buf = ::std::string::String::new();
216
217                match self {
218                    $(
219                        Self::$variant { $($inner),+ } =>
220                            ::core::write!(&mut buf, $format)?,
221                    )*
222                };
223
224                fmt.write_str(&buf)
225            }
226        }
227    };
228
229    ($ty:ty: $($variant:ident { $($inner:ident),+ } => $format:literal),+ ,) => {
230        $crate::impl_display_enum!($ty: $($variant ($($inner),+) => $format),+);
231    };
232
233    (iou @ $ident:ident) => {
234        $ident
235    };
236
237    // IDENT-or-underscore
238    (iou @ $ident:ident) => {
239        $ident
240    };
241
242    // ident-or-UNDERSCORE
243    (iou @ _) => {
244        _
245    };
246
247
248    // TODO: mixed named and positional variant support
249}
250
251#[cfg(test)]
252mod tests {
253    use alloc::{
254        borrow::ToOwned as _,
255        string::{String, ToString as _},
256    };
257
258    #[test]
259    fn impl_forward_for_newtype_struct() {
260        struct Foo(String);
261
262        forward_display!(Foo);
263
264        assert_eq!(Foo("hello world".to_owned()).to_string(), "hello world");
265    }
266
267    #[test]
268    fn impl_forward_newtype_named_struct() {
269        struct Foo {
270            inner: u64,
271        }
272
273        forward_display!(Foo => inner);
274
275        assert_eq!(Foo { inner: 42 }.to_string(), "42");
276    }
277
278    #[test]
279    fn impl_forward_generic_newtype_struct() {
280        struct Foo<T>(T);
281
282        forward_display!(<T> in Foo<T>);
283
284        assert_eq!(Foo(42).to_string(), "42");
285    }
286
287    #[test]
288    fn impl_forward_generic_named_struct() {
289        struct Foo<T> {
290            inner: T,
291        }
292
293        forward_display!(<T> in Foo<T> => inner);
294
295        assert_eq!(Foo { inner: 42 }.to_string(), "42");
296    }
297
298    #[test]
299    fn impl_basic_for_unit_struct() {
300        struct Foo;
301        impl_display!(Foo: "foo");
302        assert_eq!(Foo.to_string(), "foo");
303    }
304
305    #[test]
306    fn impl_basic_with_args() {
307        struct Foo;
308        impl_display!(Foo: "foo {} {}", 2, 3);
309        assert_eq!(Foo.to_string(), "foo 2 3");
310    }
311
312    #[test]
313    fn impl_basic_with_inline_args() {
314        const HI: &str = "hello";
315
316        struct Hello3;
317        impl_display!(Hello3: "{HI} world");
318        assert_eq!(Hello3.to_string(), "hello world");
319    }
320}