| 1 | use std::fmt::Write as _; |
| 2 | |
| 3 | use proc_macro2::TokenStream as TokenStream2; |
| 4 | use quote::{format_ident, quote}; |
| 5 | use syn::{parse_quote, Field, Fields, Index, Type, WherePredicate}; |
| 6 | |
| 7 | use crate::consts; |
| 8 | |
| 9 | pub(crate) fn codegen( |
| 10 | fields: &Fields, |
| 11 | format_string: &mut String, |
| 12 | patterns: &mut Vec<TokenStream2>, |
| 13 | defmt_path: &syn::Path, |
| 14 | ) -> syn::Result<(Vec<TokenStream2>, Vec<WherePredicate>)> { |
| 15 | let (fields, fields_are_named) = match fields { |
| 16 | Fields::Named(named) => (&named.named, true), |
| 17 | Fields::Unit => return Ok((vec![], vec![])), |
| 18 | Fields::Unnamed(unnamed) => (&unnamed.unnamed, false), |
| 19 | }; |
| 20 | |
| 21 | if fields.is_empty() { |
| 22 | return Ok((vec![], vec![])); |
| 23 | } |
| 24 | |
| 25 | if fields_are_named { |
| 26 | format_string.push_str(" {{ " ); |
| 27 | } else { |
| 28 | format_string.push('(' ); |
| 29 | } |
| 30 | |
| 31 | let mut stmts = vec![]; |
| 32 | let mut where_predicates = vec![]; |
| 33 | let mut is_first = true; |
| 34 | for (index, field) in fields.iter().enumerate() { |
| 35 | if is_first { |
| 36 | is_first = false; |
| 37 | } else { |
| 38 | format_string.push_str(", " ); |
| 39 | } |
| 40 | |
| 41 | let format_opt = get_defmt_format_option(field)?; |
| 42 | // Find out if the field type is natively supported by defmt. `ty` will be None if not. |
| 43 | let ty = as_native_type(&field.ty); |
| 44 | // `field_ty` will be the field's type if it is not natively supported by defmt |
| 45 | let field_ty = if ty.is_none() { Some(&field.ty) } else { None }; |
| 46 | // Get the field format specifier. Either the native specifier or '?'. |
| 47 | let ty = ty.unwrap_or_else(|| consts::TYPE_FORMAT.to_string()); |
| 48 | let ident = field |
| 49 | .ident |
| 50 | .clone() |
| 51 | .unwrap_or_else(|| format_ident!("arg {}" , index)); |
| 52 | // Find the required trait bounds for the field and add the formatting statement depending on the field type and the formatting options |
| 53 | let bound: Option<syn::Path> = if let Some(FormatOption::Debug2Format) = format_opt { |
| 54 | stmts.push(quote!(#defmt_path::export::fmt(&#defmt_path::Debug2Format(&#ident)))); |
| 55 | field_ty.map(|_| parse_quote!(::core::fmt::Debug)) |
| 56 | } else if let Some(FormatOption::Display2Format) = format_opt { |
| 57 | stmts.push(quote!(#defmt_path::export::fmt(&#defmt_path::Display2Format(&#ident)))); |
| 58 | field_ty.map(|_| parse_quote!(::core::fmt::Display)) |
| 59 | } else if ty == consts::TYPE_FORMAT { |
| 60 | stmts.push(quote!(#defmt_path::export::fmt(#ident))); |
| 61 | field_ty.map(|_| parse_quote!(#defmt_path::Format)) |
| 62 | } else { |
| 63 | let method = format_ident!(" {}" , ty); |
| 64 | stmts.push(quote!(#defmt_path::export::#method(#ident))); |
| 65 | field_ty.map(|_| parse_quote!(#defmt_path::Format)) |
| 66 | }; |
| 67 | if let Some(bound) = bound { |
| 68 | where_predicates.push(parse_quote!(#field_ty: #bound)); |
| 69 | } |
| 70 | |
| 71 | if field.ident.is_some() { |
| 72 | // Named field. |
| 73 | write!(format_string, " {ident}: {{= {ty}:? }}" ).ok(); |
| 74 | |
| 75 | patterns.push(quote!( #ident )); |
| 76 | } else { |
| 77 | // Unnamed (tuple) field. |
| 78 | write!(format_string, " {{= {ty}}}" ).ok(); |
| 79 | |
| 80 | let index = Index::from(index); |
| 81 | patterns.push(quote!( #index: #ident )); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | if fields_are_named { |
| 86 | format_string.push_str(" }}" ); |
| 87 | } else { |
| 88 | format_string.push(')' ); |
| 89 | } |
| 90 | |
| 91 | Ok((stmts, where_predicates)) |
| 92 | } |
| 93 | |
| 94 | #[derive (Copy, Clone, Eq, PartialEq, Debug)] |
| 95 | enum FormatOption { |
| 96 | Debug2Format, |
| 97 | Display2Format, |
| 98 | } |
| 99 | |
| 100 | /// If the field has a valid defmt attribute (e.g. `#[defmt(Debug2Format)]`), returns `Ok(Some(FormatOption))`. |
| 101 | /// Returns `Err` if we can't parse a valid defmt attribute. |
| 102 | /// Returns `Ok(None)` if there are no `defmt` attributes on the field. |
| 103 | fn get_defmt_format_option(field: &Field) -> syn::Result<Option<FormatOption>> { |
| 104 | let mut format_option = None; |
| 105 | |
| 106 | for attr in &field.attrs { |
| 107 | if attr.path().is_ident("defmt" ) { |
| 108 | if format_option.is_some() { |
| 109 | return Err(syn::Error::new_spanned( |
| 110 | field, |
| 111 | "multiple `defmt` attributes not supported" , |
| 112 | )); |
| 113 | } |
| 114 | |
| 115 | let mut parsed_format = None; |
| 116 | |
| 117 | attr.parse_nested_meta(|meta| { |
| 118 | // #[defmt(Debug2Format)] |
| 119 | if meta.path.is_ident("Debug2Format" ) { |
| 120 | parsed_format = Some(FormatOption::Debug2Format); |
| 121 | return Ok(()); |
| 122 | } |
| 123 | |
| 124 | // #[defmt(Display2Format)] |
| 125 | if meta.path.is_ident("Display2Format" ) { |
| 126 | parsed_format = Some(FormatOption::Display2Format); |
| 127 | return Ok(()); |
| 128 | } |
| 129 | |
| 130 | Err(meta.error("expected `Debug2Format` or `Display2Format`" )) |
| 131 | })?; |
| 132 | |
| 133 | if parsed_format.is_none() { |
| 134 | return Err(syn::Error::new_spanned( |
| 135 | &attr.meta, |
| 136 | "expected 1 attribute argument" , |
| 137 | )); |
| 138 | } |
| 139 | |
| 140 | format_option = parsed_format; |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | Ok(format_option) |
| 145 | } |
| 146 | |
| 147 | /// Returns `Some` if `ty` refers to a builtin Rust type that has native support from defmt and does |
| 148 | /// not have to go through the `Format` trait. |
| 149 | /// |
| 150 | /// This should return `Some` for all types that can be used as `{=TYPE}`. |
| 151 | /// |
| 152 | /// Note: This is technically incorrect, since builtin types can be shadowed. However the efficiency |
| 153 | /// gains are too big to pass up, so we expect user code to not do that. |
| 154 | fn as_native_type(ty: &Type) -> Option<String> { |
| 155 | match ty { |
| 156 | Type::Path(path: &TypePath) => { |
| 157 | let ident: &Ident = path.path.get_ident()?; |
| 158 | let ty_name: String = ident.to_string(); |
| 159 | |
| 160 | match &*ty_name { |
| 161 | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" |
| 162 | | "i128" | "isize" | "f32" | "f64" | "bool" | "str" => Some(ty_name), |
| 163 | _ => None, |
| 164 | } |
| 165 | } |
| 166 | Type::Reference(ty_ref: &TypeReference) => as_native_type(&ty_ref.elem), |
| 167 | _ => None, |
| 168 | } |
| 169 | } |
| 170 | |