cido-macros 0.2.0

Macros for generating code that enables easier interfacing with cido
Documentation
use crate::parse::deps::*;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::{Attribute, Type};

use super::TransformerField;

impl crate::codegen::CodegenWrapper<super::TransformerStruct> {
  pub(crate) fn gen_structs(&self, token_stream: &mut TokenStream) {
    self
      .gen_structs_inner(
        &self.attrs,
        self.struct_name(),
        |f| f.field.attrs.clone(),
        |f| &f.name,
        TransformerField::self_type,
      )
      .to_tokens(token_stream);

    self
      .gen_structs_inner(
        &[],
        &self.graphql_input_name(),
        |_| Vec::default(),
        |f| f.graphql_input_name(),
        TransformerField::graphql_input_type,
      )
      .to_tokens(token_stream);

    self.gen_hash_impls().to_tokens(token_stream);

    self
      .gen_graphql_output_struct_inner()
      .to_tokens(token_stream);
  }

  fn gen_graphql_output_struct_inner(&self) -> TokenStream {
    let struct_name = self.graphql_output_name();
    let struct_attrs = self.struct_opts.graphql_opts.attrs.outer_attributes();
    let struct_vis = &self.vis;
    let fields = self.fields.iter().map(|f| {
      let attrs = f.graphql_config.attrs.outer_attributes();
      let field_vis = &f.field.vis;
      let field_name = &f.name;
      let field_type = &f.self_type();
      quote! {
        #(#attrs)*
        #field_vis #field_name: #field_type
      }
    });
    let self_name = self.struct_name();

    let block_ty = quote!(<<<#self_name as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockNumber);

    let block_fields = if self.is_entity() {
      quote! {
        pub(crate) __block_number_range: <#block_ty as ::cido::__internal::BlockNumber>::Range,
      }
    } else {
      quote! {
        pub(crate) __block_number: #block_ty,
      }
    };

    quote! {
      #(#struct_attrs)*
      #[allow(non_snake_case)]
      #struct_vis struct #struct_name {
        pub(crate) __query_block: #block_ty,
        #block_fields
        #(#fields),*
      }

      impl #struct_name {
        pub fn __query_block(&self) -> #block_ty {
          self.__query_block
        }
        pub fn __set_query_block(&mut self, query_block: #block_ty) {
          self.__query_block = query_block;
        }
      }
    }
  }

  fn gen_structs_inner(
    &self,
    struct_attrs: &[Attribute],
    struct_name: &Ident,
    field_attrs: impl Fn(&TransformerField) -> Vec<Attribute>,
    field_name: impl Fn(&TransformerField) -> &Ident,
    field_type: impl Fn(&TransformerField) -> Type,
  ) -> TokenStream {
    let struct_vis = &self.vis;
    let mut alias_accessors = vec![];
    let fields = self.fields.iter().map(|f| {
      let attrs = field_attrs(f);
      let field_vis = &f.field.vis;
      let field_name = field_name(f);
      let field_type = field_type(f);

      f.alias.iter().for_each(|alias| {
        let setter = format_ident!("set_{}", alias);
        let getter = format_ident!("get_{}", alias);
        alias_accessors.push(quote! {
          #field_vis fn #setter(&mut self, val: #field_type) {
            self.#field_name = val;
          }

          #field_vis fn #getter(&self) -> &#field_type {
            &self.#field_name
          }
        });
      });

      quote! {
        #(#attrs)*
        #field_vis #field_name: #field_type
      }
    });

    quote! {
      #[allow(non_snake_case)]
      #(#struct_attrs)*
      #struct_vis struct #struct_name {
        #(#fields),*
      }

      impl #struct_name {
        #(#alias_accessors)*
      }
    }
  }

  #[allow(unused_assignments, unused_mut)]
  fn gen_hash_impls(&self) -> TokenStream {
    let struct_name = self.struct_name();

    let fast_fields_hashing = self.fields.iter().enumerate().map(|(i, f)| {
      let field_name = &f.name;
      let field_name_str = f.graphql_output_name().to_string();
      quote! {
        {
          let i = #i;
          // Use tuple impl from stable-hash
          // let field_hashing = (#field_name_str, &self.#field_name);
          // cannot use tuple here since it won't handle hashing the Value type for us at the end
          let field_hashing = ::cido::__internal::ValueTypeHasher {
            field: #field_name_str,
            data: &self.#field_name,
          };
          // Must create an independent hasher to "break" relationship between
          // independent field addresses.
          let mut new_hasher = H::new();
          let (a, b) = field_address.unordered();
          field_hashing.stable_hash(a, &mut new_hasher);
          state.write(b, new_hasher.to_bytes().as_ref());
          // ::cido::__internal::stable_hash::hash_debug!("member {i}: {}", ::cido::__internal::stable_hash::hex::encode(state.to_bytes()));
        }
      }
    });

    // let start_hashing = format!("start hashing struct fields for {struct_name}");
    // let end_hashing = format!("end hashing struct fields for {struct_name}");
    quote! {
      impl ::cido::__internal::stable_hash::StableHash for #struct_name {
        fn stable_hash<H: ::cido::__internal::stable_hash::StableHasher>(
          &self,
          field_address: H::Addr,
          state: &mut H
        ) {
          use ::cido::__internal::stable_hash::prelude::*;
          use ::cido::__internal::{BigDecimal, BigInt, Hashable};
          // let d = ::cido::__internal::stable_hash::CallDepth::new();
          // ::cido::__internal::stable_hash::hash_debug!(#start_hashing);
          #(#fast_fields_hashing)*
          // ::cido::__internal::stable_hash::hash_debug!(#end_hashing);
        }
      }
    }
  }
}