1 | use proc_macro2::{Ident, TokenStream}; |
2 | use quote::quote; |
3 | use syn::{punctuated::Punctuated, Data, DeriveInput, Fields, LitStr, Token}; |
4 | |
5 | use crate::helpers::{ |
6 | non_enum_error, non_single_field_variant_error, HasStrumVariantProperties, HasTypeProperties, |
7 | }; |
8 | |
9 | pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> { |
10 | let name = &ast.ident; |
11 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); |
12 | let variants = match &ast.data { |
13 | Data::Enum(v) => &v.variants, |
14 | _ => return Err(non_enum_error()), |
15 | }; |
16 | |
17 | let type_properties = ast.get_type_properties()?; |
18 | |
19 | let mut arms = Vec::new(); |
20 | for variant in variants { |
21 | let ident = &variant.ident; |
22 | let variant_properties = variant.get_variant_properties()?; |
23 | |
24 | if variant_properties.disabled.is_some() { |
25 | continue; |
26 | } |
27 | |
28 | if let Some(..) = variant_properties.transparent { |
29 | let arm = super::extract_single_field_variant_and_then(name, variant, |tok| { |
30 | quote! { ::core::fmt::Display::fmt(#tok, f) } |
31 | }) |
32 | .map_err(|_| non_single_field_variant_error("transparent" ))?; |
33 | |
34 | arms.push(arm); |
35 | continue; |
36 | } |
37 | |
38 | // Look at all the serialize attributes. |
39 | let output = variant_properties |
40 | .get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref()); |
41 | |
42 | let params = match variant.fields { |
43 | Fields::Unit => quote! {}, |
44 | Fields::Unnamed(ref unnamed_fields) => { |
45 | // Transform unnamed params '(String, u8)' to '(ref field0, ref field1)' |
46 | let names: Punctuated<_, Token!(,)> = unnamed_fields |
47 | .unnamed |
48 | .iter() |
49 | .enumerate() |
50 | .map(|(index, field)| { |
51 | assert!(field.ident.is_none()); |
52 | let ident = |
53 | syn::parse_str::<Ident>(format!("field {}" , index).as_str()).unwrap(); |
54 | quote! { ref #ident } |
55 | }) |
56 | .collect(); |
57 | quote! { (#names) } |
58 | } |
59 | Fields::Named(ref field_names) => { |
60 | // Transform named params '{ name: String, age: u8 }' to '{ ref name, ref age }' |
61 | let names: Punctuated<TokenStream, Token!(,)> = field_names |
62 | .named |
63 | .iter() |
64 | .map(|field| { |
65 | let ident = field.ident.as_ref().unwrap(); |
66 | quote! { ref #ident } |
67 | }) |
68 | .collect(); |
69 | |
70 | quote! { {#names} } |
71 | } |
72 | }; |
73 | |
74 | if variant_properties.to_string.is_none() && variant_properties.default.is_some() { |
75 | let arm = super::extract_single_field_variant_and_then(name, variant, |tok| { |
76 | quote! { ::core::fmt::Display::fmt(#tok, f)} |
77 | }) |
78 | .map_err(|_| { |
79 | syn::Error::new_spanned( |
80 | variant, |
81 | "Default only works on newtype structs with a single String field" , |
82 | ) |
83 | })?; |
84 | |
85 | arms.push(arm); |
86 | continue; |
87 | } |
88 | |
89 | let arm = match variant.fields { |
90 | Fields::Named(ref field_names) => { |
91 | let used_vars = capture_format_string_idents(&output)?; |
92 | if used_vars.is_empty() { |
93 | quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) } |
94 | } else { |
95 | // Create args like 'name = name, age = age' for format macro |
96 | let args: Punctuated<_, Token!(,)> = field_names |
97 | .named |
98 | .iter() |
99 | .filter_map(|field| { |
100 | let ident = field.ident.as_ref().unwrap(); |
101 | // Only contain variables that are used in format string |
102 | if !used_vars.contains(ident) { |
103 | None |
104 | } else { |
105 | Some(quote! { #ident = #ident }) |
106 | } |
107 | }) |
108 | .collect(); |
109 | |
110 | quote! { |
111 | #[allow(unused_variables)] |
112 | #name::#ident #params => ::core::fmt::Display::fmt(&format_args!(#output, #args), f) |
113 | } |
114 | } |
115 | } |
116 | Fields::Unnamed(ref unnamed_fields) => { |
117 | let used_vars = capture_format_strings(&output)?; |
118 | if used_vars.iter().any(String::is_empty) { |
119 | return Err(syn::Error::new_spanned( |
120 | &output, |
121 | "Empty {} is not allowed; Use manual numbering ({0})" , |
122 | )); |
123 | } |
124 | if used_vars.is_empty() { |
125 | quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) } |
126 | } else { |
127 | let args: Punctuated<_, Token!(,)> = unnamed_fields |
128 | .unnamed |
129 | .iter() |
130 | .enumerate() |
131 | .map(|(index, field)| { |
132 | assert!(field.ident.is_none()); |
133 | syn::parse_str::<Ident>(format!("field {}" , index).as_str()).unwrap() |
134 | }) |
135 | .collect(); |
136 | quote! { |
137 | #[allow(unused_variables)] |
138 | #name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f) |
139 | } |
140 | } |
141 | } |
142 | Fields::Unit => { |
143 | let used_vars = capture_format_strings(&output)?; |
144 | if !used_vars.is_empty() { |
145 | return Err(syn::Error::new_spanned( |
146 | &output, |
147 | "Unit variants do not support interpolation" , |
148 | )); |
149 | } |
150 | |
151 | quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) } |
152 | } |
153 | }; |
154 | |
155 | arms.push(arm); |
156 | } |
157 | |
158 | if arms.len() < variants.len() { |
159 | arms.push(quote! { _ => panic!("fmt() called on disabled variant." ) }); |
160 | } |
161 | |
162 | Ok(quote! { |
163 | impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause { |
164 | fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::result::Result<(), ::core::fmt::Error> { |
165 | match *self { |
166 | #(#arms),* |
167 | } |
168 | } |
169 | } |
170 | }) |
171 | } |
172 | |
173 | fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Ident>> { |
174 | capture_format_stringsimpl Iterator- >
(string_literal)? |
175 | .into_iter() |
176 | .map(|ident: String| { |
177 | syn::parse_str::<Ident>(ident.as_str()).map_err(|_| { |
178 | syn::Error::new_spanned( |
179 | tokens:string_literal, |
180 | message:"Invalid identifier inside format string bracket" , |
181 | ) |
182 | }) |
183 | }) |
184 | .collect() |
185 | } |
186 | |
187 | fn capture_format_strings(string_literal: &LitStr) -> syn::Result<Vec<String>> { |
188 | // Remove escaped brackets |
189 | let format_str = string_literal.value().replace("{{" , "" ).replace("}}" , "" ); |
190 | |
191 | let mut new_var_start_index: Option<usize> = None; |
192 | let mut var_used = Vec::new(); |
193 | |
194 | for (i, chr) in format_str.bytes().enumerate() { |
195 | if chr == b'{' { |
196 | if new_var_start_index.is_some() { |
197 | return Err(syn::Error::new_spanned( |
198 | string_literal, |
199 | "Bracket opened without closing previous bracket" , |
200 | )); |
201 | } |
202 | new_var_start_index = Some(i); |
203 | continue; |
204 | } |
205 | |
206 | if chr == b'}' { |
207 | let start_index = new_var_start_index.take().ok_or(syn::Error::new_spanned( |
208 | string_literal, |
209 | "Bracket closed without previous opened bracket" , |
210 | ))?; |
211 | |
212 | let inside_brackets = &format_str[start_index + 1..i]; |
213 | let ident_str = inside_brackets.split(":" ).next().unwrap().trim_end(); |
214 | var_used.push(ident_str.to_owned()); |
215 | } |
216 | } |
217 | |
218 | Ok(var_used) |
219 | } |
220 | |