Skip to main content

rtformat_derive/
lib.rs

1//! Derive macros for [`rtformat`](https://docs.rs/rtformat).
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, DeriveInput};
6
7/// Derives `rtformat::FormatArg`, adapting to whichever of `Display` and
8/// `Debug` the type implements — implementing either one is enough.
9///
10/// # Routing
11///
12/// - `{}` renders via `Display` when available, falling back to `Debug`.
13/// - `{:?}` and `{:#?}` render via `Debug` (pretty in the `#` case).
14/// - Any other format type is rejected with `Err(fmt::Error)` (surfaced as
15///   `rtformat::FormatError::UnsupportedFormatType`), as is `{}` on a type
16///   implementing neither trait and `{:?}` on a `Display`-only type.
17///
18/// The dispatch is resolved statically at compile time through
19/// inherent-vs-trait method priority, so there is no runtime cost to the
20/// adaptation and a missing trait surfaces as a runtime
21/// `UnsupportedFormatType` error rather than a compile failure.
22///
23/// # Generic types
24///
25/// Every type parameter is additionally bound by `Display + Debug`
26/// (serde-style): trait availability on `Self` must be known when the
27/// derived impl is checked, which only holds if the parameters provide it.
28///
29/// # Examples
30///
31/// ```ignore
32/// use core::fmt;
33/// use rtformat::{rformat, Format, FormatArg};
34///
35/// #[derive(Debug, FormatArg)]
36/// struct Color(u8, u8, u8);
37///
38/// impl fmt::Display for Color {
39///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40///         write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
41///     }
42/// }
43///
44/// assert_eq!(rformat!("{}", Color(255, 128, 0)), "#ff8000");
45/// assert_eq!(rformat!("{:?}", Color(255, 128, 0)), "Color(255, 128, 0)");
46/// ```
47#[proc_macro_derive(FormatArg)]
48pub fn derive_format_arg(input: TokenStream) -> TokenStream {
49    let input = parse_macro_input!(input as DeriveInput);
50    let name = &input.ident;
51    let mut generics = input.generics.clone();
52    for param in generics.type_params_mut() {
53        param.bounds.push(syn::parse_quote!(::core::fmt::Display));
54        param.bounds.push(syn::parse_quote!(::core::fmt::Debug));
55    }
56    let (impl_generics, _, _) = generics.split_for_impl();
57    let (_, ty_generics, where_clause) = input.generics.split_for_impl();
58    let expanded = quote! {
59        impl #impl_generics ::rtformat::FormatArg for #name #ty_generics #where_clause {
60            fn write(
61                &self,
62                ty: ::rtformat::FormatType,
63                pretty: bool,
64                _precision: ::core::option::Option<::core::primitive::usize>,
65                f: &mut ::core::fmt::Formatter<'_>,
66            ) -> ::core::fmt::Result {
67                #[allow(unused_imports)]
68                use ::rtformat::__private::{DebugFallback as _, DisplayFallback as _};
69                let wrap = ::rtformat::__private::Wrap(self);
70                match ty {
71                    ::rtformat::FormatType::Display => wrap
72                        .fmt_display(f)
73                        .or_else(|_| wrap.fmt_debug(f))
74                        .unwrap_or(::core::result::Result::Err(::core::fmt::Error)),
75                    ::rtformat::FormatType::Debug if pretty => wrap
76                        .fmt_debug_pretty(f)
77                        .unwrap_or(::core::result::Result::Err(::core::fmt::Error)),
78                    ::rtformat::FormatType::Debug => wrap
79                        .fmt_debug(f)
80                        .unwrap_or(::core::result::Result::Err(::core::fmt::Error)),
81                    _ => ::core::result::Result::Err(::core::fmt::Error),
82                }
83            }
84        }
85    };
86    TokenStream::from(expanded)
87}