defmt-macros 1.0.0

defmt macros
Documentation
use proc_macro::TokenStream;
use proc_macro_error2::{abort, abort_call_site};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, Data, DeriveInput};

mod codegen;

pub(crate) fn expand(input: TokenStream) -> TokenStream {
    let DeriveInput {
        attrs,
        vis: _,
        ident,
        mut generics,
        data,
    } = parse_macro_input!(input as DeriveInput);

    let defmt_path = match defmt_crate_path(&attrs) {
        Ok(defmt_path) => defmt_path,
        Err(e) => abort!(e),
    };

    let encode_data = match &data {
        Data::Enum(data) => codegen::encode_enum_data(&ident, data, &defmt_path),
        Data::Struct(data) => codegen::encode_struct_data(&ident, data, &defmt_path),
        Data::Union(_) => abort_call_site!("`#[derive(Format)]` does not support unions"),
    };

    let codegen::EncodeData {
        format_tag,
        stmts,
        where_predicates,
    } = match encode_data {
        Ok(data) => data,
        Err(e) => return e.into_compile_error().into(),
    };

    let codegen::Generics {
        impl_generics,
        type_generics,
        where_clause,
    } = codegen::Generics::codegen(&mut generics, where_predicates);

    quote!(
        #[automatically_derived]
        impl #impl_generics #defmt_path::Format for #ident #type_generics #where_clause {
            fn format(&self, f: #defmt_path::Formatter) {
                use #defmt_path as defmt;
                #defmt_path::unreachable!()
            }

            fn _format_tag() -> #defmt_path::Str {
                #format_tag
            }

            fn _format_data(&self) {
                #(#stmts)*
            }
        }
    )
    .into()
}

fn defmt_crate_path(attrs: &[syn::Attribute]) -> Result<syn::Path, syn::Error> {
    let mut defmt_path = parse_quote!(::defmt);
    let res = attrs
        .iter()
        .filter(|attr| attr.path().is_ident("defmt"))
        .try_for_each(|attr| {
            attr.parse_nested_meta(|meta| {
                if meta.path.get_ident().is_some_and(|ident| ident == "crate") {
                    meta.input.parse::<syn::Token![=]>()?;
                    defmt_path = meta.input.parse::<syn::Path>()?;
                    Ok(())
                } else {
                    let path = meta.path.to_token_stream().to_string().replace(' ', "");
                    Err(meta.error(format_args!("unknown defmt attribute `{path}`")))
                }
            })
        });
    res.map(|()| defmt_path)
}