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