evitable-derive-core 0.5.0

Derive implementation for evitable
Documentation
use super::*;
use syn::{Lit, LitInt, LitStr};

#[derive(Debug)]
pub(crate) enum FieldRef {
  Ident(Ident),
  Index(LitInt),
}

impl ToTokens for FieldRef {
  fn to_tokens(&self, tokens: &mut TokenStream) {
    match self {
      FieldRef::Ident(i) => i.to_tokens(tokens),
      FieldRef::Index(i) => i.to_tokens(tokens),
    }
  }
}

impl FromMeta for FieldRef {
  fn from_ident(value: &Ident) -> Result<Self> {
    Ok(FieldRef::Ident(value.clone()))
  }

  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
    Ok(FieldRef::Ident(syn::Ident::new(value, span.span())))
  }

  fn from_lit(lit: &Lit) -> Result<Self> {
    (match lit {
      Lit::Bool(b) => Self::from_bool(b.value, b),
      Lit::Str(s) => Self::from_string(&s.value(), s),
      Lit::Char(c) => Self::from_char(c.value(), c),
      Lit::Int(i) => Ok(FieldRef::Index(i.clone())),
      _ => Err(Error::unexpected_lit_type(lit)),
    })
    .map_err(|e| e.with_span(lit))
  }
}

#[derive(Debug)]
pub(crate) struct FormatExpression {
  format: LitStr,
  args: Vec<FieldRef>,
}

#[derive(Debug)]
pub(crate) enum Description {
  String(LitStr),
  FormatExpression(FormatExpression),
}

impl FromMeta for Description {
  fn from_lit(lit: &Lit) -> Result<Self> {
    (match lit {
      Lit::Bool(b) => Self::from_bool(b.value, b),
      Lit::Str(s) => Ok(Description::String(s.clone())),
      Lit::Char(c) => Self::from_char(c.value(), c),
      Lit::Int(i) => Self::from_int(i.base10_parse()?, i),
      _ => Err(Error::unexpected_lit_type(lit)),
    })
    .map_err(|e| e.with_span(lit))
  }

  fn from_list(items: &[&NestedMeta]) -> Result<Self> {
    match items.len() {
      0 => Self::from_empty(),
      1 => Self::from_nested_meta(&items[0]),
      n => {
        let format = <LitStr as FromMeta>::from_nested_meta(&items[0])?;
        let mut args = Vec::with_capacity(n - 1);
        for item in items.iter().skip(1) {
          args.push(FromMeta::from_nested_meta(item)?);
        }

        Ok(Description::FormatExpression(FormatExpression {
          format,
          args,
        }))
      }
    }
  }
}

pub(crate) struct ResolvedDescription {
  format: LitStr,
  args: Vec<TokenStream>,
}

impl Description {
  fn resolve(
    self,
    lookup: impl Fn(FieldRef) -> Result<TokenStream>,
  ) -> Result<ResolvedDescription> {
    match self {
      Description::String(s) => Ok(ResolvedDescription {
        format: s,
        args: Vec::with_capacity(0),
      }),

      Description::FormatExpression(f) => Ok(ResolvedDescription {
        format: f.format,
        args: f.args.into_iter().map(lookup).collect::<Result<Vec<_>>>()?,
      }),
    }
  }

  pub fn resolve_from_inst<'a, T, I: IntoIdent<'a>>(
    self,
    fields: &Fields<T>,
    ident: I,
  ) -> Result<ResolvedDescription> {
    let ident = &ident.into_ident();

    match fields {
      Fields::Unit => self.resolve(|r| match r {
        FieldRef::Ident(i) => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
        FieldRef::Index(i) => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
      }),
      Fields::Named(f) => self.resolve(|r| match r {
        FieldRef::Index(i) => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
        FieldRef::Ident(i) => match f.iter().find(|(f, _)| f.to_string() == i.to_string()) {
          None => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
          Some(_) => Ok(quote! { #ident.#i }),
        },
      }),
      Fields::Unnamed(f) => self.resolve(|r| match r {
        FieldRef::Ident(i) => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
        FieldRef::Index(i) => match f
          .iter()
          .find(|(f, _)| *f as u64 == i.base10_parse().unwrap())
        {
          None => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
          Some(_) => Ok(quote! { #ident.#i }),
        },
      }),
    }
  }

  pub fn resolve_from_variant<T>(self, fields: &Fields<T>) -> Result<ResolvedDescription> {
    match fields {
      Fields::Unit => self.resolve(|r| match r {
        FieldRef::Ident(i) => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
        FieldRef::Index(i) => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
      }),
      Fields::Named(f) => self.resolve(|r| match r {
        FieldRef::Index(i) => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
        FieldRef::Ident(i) => match f.iter().find(|(f, _)| f.to_string() == i.to_string()) {
          None => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
          Some(_) => Ok(quote! { #i }),
        },
      }),
      Fields::Unnamed(f) => self.resolve(|r| match r {
        FieldRef::Ident(i) => Err(Error::unknown_field(&i.to_string()).with_span(&i)),
        FieldRef::Index(i) => match f
          .iter()
          .find(|(f, _)| *f as u64 == i.base10_parse().unwrap())
        {
          None => Err(Error::unknown_field(i.base10_digits()).with_span(&i)),
          Some((i, _)) => Ok({
            let ident = i.into_ident();
            quote! { #ident }
          }),
        },
      }),
    }
  }
}

impl ToTokens for ResolvedDescription {
  fn to_tokens(&self, tokens: &mut TokenStream) {
    let format = &self.format;
    if self.args.len() == 0 {
      tokens.extend(quote! {
        f.write_str(#format)
      });
    } else {
      let args = &self.args;
      tokens.extend(quote! {
        write!(f, #format, #(#args),*)
      });
    }
  }
}