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 | |