use std::fmt::Write as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{Field, Fields, Index, Type};
use crate::consts;
pub(crate) fn codegen(
fields: &Fields,
format_string: &mut String,
patterns: &mut Vec<TokenStream2>,
) -> syn::Result<Vec<TokenStream2>> {
let (fields, fields_are_named) = match fields {
Fields::Named(named) => (&named.named, true),
Fields::Unit => return Ok(vec![]),
Fields::Unnamed(unnamed) => (&unnamed.unnamed, false),
};
if fields.is_empty() {
return Ok(vec![]);
}
if fields_are_named {
format_string.push_str(" {{ ");
} else {
format_string.push('(');
}
let mut stmts = vec![];
let mut is_first = true;
for (index, field) in fields.iter().enumerate() {
if is_first {
is_first = false;
} else {
format_string.push_str(", ");
}
let format_opt = get_defmt_format_option(field)?;
let ty = as_native_type(&field.ty).unwrap_or_else(|| consts::TYPE_FORMAT.to_string());
let ident = field
.ident
.clone()
.unwrap_or_else(|| format_ident!("arg{}", index));
if let Some(FormatOption::Debug2Format) = format_opt {
stmts.push(quote!(defmt::export::fmt(&defmt::Debug2Format(&#ident))));
} else if let Some(FormatOption::Display2Format) = format_opt {
stmts.push(quote!(defmt::export::fmt(&defmt::Display2Format(&#ident))));
} else if ty == consts::TYPE_FORMAT {
stmts.push(quote!(defmt::export::fmt(#ident)));
} else {
let method = format_ident!("{}", ty);
stmts.push(quote!(defmt::export::#method(#ident)));
}
if field.ident.is_some() {
write!(format_string, "{ident}: {{={ty}:?}}").ok();
patterns.push(quote!( #ident ));
} else {
write!(format_string, "{{={ty}}}").ok();
let index = Index::from(index);
patterns.push(quote!( #index: #ident ));
}
}
if fields_are_named {
format_string.push_str(" }}");
} else {
format_string.push(')');
}
Ok(stmts)
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum FormatOption {
Debug2Format,
Display2Format,
}
fn get_defmt_format_option(field: &Field) -> syn::Result<Option<FormatOption>> {
let mut format_option = None;
for attr in &field.attrs {
if attr.path().is_ident("defmt") {
if format_option.is_some() {
return Err(syn::Error::new_spanned(
field,
"multiple `defmt` attributes not supported",
));
}
let mut parsed_format = None;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("Debug2Format") {
parsed_format = Some(FormatOption::Debug2Format);
return Ok(());
}
if meta.path.is_ident("Display2Format") {
parsed_format = Some(FormatOption::Display2Format);
return Ok(());
}
Err(meta.error("expected `Debug2Format` or `Display2Format`"))
})?;
if parsed_format.is_none() {
return Err(syn::Error::new_spanned(
&attr.meta,
"expected 1 attribute argument",
));
}
format_option = parsed_format;
}
}
Ok(format_option)
}
fn as_native_type(ty: &Type) -> Option<String> {
match ty {
Type::Path(path) => {
let ident = path.path.get_ident()?;
let ty_name = ident.to_string();
match &*ty_name {
"u8" | "u16" | "u32" | "usize" | "i8" | "i16" | "i32" | "isize" | "f32" | "f64"
| "bool" | "str" => Some(ty_name),
_ => None,
}
}
Type::Reference(ty_ref) => as_native_type(&ty_ref.elem),
_ => None,
}
}