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}