//! Helper functions for deriving `Display` use crate::utils; use ethers_core::{abi::ParamType, macros::ethers_core_crate}; use proc_macro2::TokenStream; use quote::quote; use syn::{parse::Error, spanned::Spanned, Data, DeriveInput, Fields, Index}; /// Derive `fmt::Display` for the given type pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result { let fields = match input.data { Data::Struct(ref data) => match data.fields { Fields::Named(ref fields) => fields.named.iter().collect(), Fields::Unnamed(ref fields) => fields.unnamed.iter().collect(), Fields::Unit => vec![], }, Data::Enum(_) => { return Err(Error::new(input.span(), "Enum types are not supported by EthDisplay")) } Data::Union(_) => { return Err(Error::new(input.span(), "Union types are not supported by EthDisplay")) } }; let mut expressions = TokenStream::new(); for (i, field) in fields.iter().enumerate() { let ident = field.ident.as_ref().map(|id| quote!(#id)).unwrap_or_else(|| { let idx = Index::from(i); quote!(#idx) }); if let Ok(param) = utils::find_parameter_type(&field.ty) { let ethers_core = ethers_core_crate(); let hex_encode = quote!(#ethers_core::utils::hex::encode); fmt_params_tokens(¶m, ident, &mut expressions, &hex_encode); } else { // could not detect the parameter type and rely on using debug fmt fmt_debug_tokens(&ident, &mut expressions); } // comma separator if i < fields.len() - 1 { let tokens = quote! { ::core::fmt::Write::write_str(f, ", ")?; }; expressions.extend(tokens); } } let name = &input.ident; Ok(quote! { impl ::core::fmt::Display for #name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #expressions Ok(()) } } }) } /// Recursive for tuples len > 12. fn fmt_params_tokens( param: &ParamType, ident: TokenStream, out: &mut TokenStream, hex_encode: &TokenStream, ) { match param { // Display ParamType::Bool | ParamType::String | ParamType::Uint(_) | ParamType::Int(_) => { fmt_display_tokens(&ident, out); } // Debug ParamType::Address => fmt_debug_tokens(&ident, out), // 0x ++ hex::encode ParamType::Bytes | ParamType::FixedBytes(_) => hex_encode_tokens(&ident, out, hex_encode), // Debug or recurse ParamType::Tuple(params) => { // Debug is implemented automatically only for tuples with arity <= 12 if params.len() <= 12 { fmt_debug_tokens(&ident, out); } else { for (i, new_param) in params.iter().enumerate() { let idx = Index::from(i); let new_ident = quote!(#ident.#idx); fmt_params_tokens(new_param, new_ident, out, hex_encode); } } } // 0x ++ hex::encode or DebugList ParamType::Array(ty) | ParamType::FixedArray(ty, _) => match &**ty { ParamType::Uint(8) => hex_encode_tokens(&ident, out, hex_encode), ParamType::Tuple(params) if params.len() > 12 => { // TODO: Recurse this let idx = (0..params.len()).map(Index::from); let tokens = quote! { let mut list = f.debug_list(); for entry in self.#ident.iter() { #( list.entry(&entry.#idx); )* } list.finish()?; }; out.extend(tokens); } _ => { let tokens = quote! { f.debug_list().entries(self.#ident.iter()).finish()?; }; out.extend(tokens); } }, } } fn fmt_display_tokens(ident: &TokenStream, out: &mut TokenStream) { let tokens = quote! { ::core::fmt::Display::fmt(&self.#ident, f)?; }; out.extend(tokens); } fn fmt_debug_tokens(ident: &TokenStream, out: &mut TokenStream) { let tokens = quote! { ::core::fmt::Debug::fmt(&self.#ident, f)?; }; out.extend(tokens); } fn hex_encode_tokens(ident: &TokenStream, out: &mut TokenStream, hex_encode: &TokenStream) { let tokens = quote! { ::core::fmt::Write::write_str(f, "0x")?; ::core::fmt::Write::write_str(f, #hex_encode(&self.#ident).as_str())?; }; out.extend(tokens); }