forward-methods 0.0.2

A derive macro for forwarding methods from composed objects
Documentation
use std::fmt::{Debug, Formatter};

use derive_builder::Builder;
use proc_macro2::Ident;
use quote::ToTokens;
use syn::{Member, PatType, Receiver, ReturnType};

#[derive(Builder, Clone, PartialEq)]
pub struct FwdDecl {
    #[builder(setter(custom))]
    pub delegate: Delegate,
    #[builder(setter(custom))]
    pub target: Member,
}

#[derive(Clone, Debug, PartialEq)]
pub enum Delegate {
    MethodList(Vec<Method>),
}

#[derive(Builder, Clone)]
pub struct Method {
    #[builder(setter(custom))]
    pub ident: Ident,
    #[builder(setter(custom))]
    pub rcv: Receiver,
    #[builder(setter(custom), default = "Vec::new()")]
    pub args: Vec<PatType>,
    #[builder(setter(custom), default = "ReturnType::Default")]
    pub ret: ReturnType,
}

impl Debug for FwdDecl {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let tokens = self.target.to_token_stream();

        write!(f, "{:?} to {}", self.delegate, tokens)
    }
}

impl PartialEq for Method {
    fn eq(&self, other: &Self) -> bool {
        self.ident == other.ident
            && eq_rcv(&self.rcv, &other.rcv)
            && eq_args(self.args.clone(), other.args.clone())
            && eq_ret(&self.ret, &other.ret)
    }
}

impl Debug for Method {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut args = Vec::<String>::new();
        for arg in &self.args {
            args.push(arg.to_token_stream().to_string())
        }

        write!(f, "fn {}({})", self.ident, args.join(", "))?;
        if let ReturnType::Type(_, ty) = &self.ret {
            write!(f, " -> {}", ty.to_token_stream())?
        }

        Ok(())
    }
}

fn eq_args(a: Vec<PatType>, b: Vec<PatType>) -> bool {
    a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| eq_pat(a, b))
}

fn eq_ret(a: &ReturnType, b: &ReturnType) -> bool {
    a.to_token_stream().to_string() == b.to_token_stream().to_string()
}

fn eq_rcv(a: &Receiver, b: &Receiver) -> bool {
    a.reference.is_some() == b.reference.is_some()
        && a.mutability.is_some() == b.mutability.is_some()
}

fn eq_pat(a: &PatType, b: &PatType) -> bool {
    a.pat.to_token_stream().to_string() == b.pat.to_token_stream().to_string()
}

#[cfg(test)]
mod tests {
    use proc_macro2::Span;
    use quote::{format_ident, quote, IdentFragment};
    use syn::{FnArg, Index, Member};

    use crate::model::{Delegate, FwdDeclBuilder, MethodBuilder};

    impl FwdDeclBuilder {
        pub fn named_target(&mut self, ident: &str) -> &mut Self {
            self.target = Some(Member::Named(format_ident!("{}", ident)));
            self
        }

        pub fn unnamed_target(&mut self, idx: u32) -> &mut Self {
            self.target = Some(Member::Unnamed(Index {
                index: idx,
                span: Span::call_site(),
            }));
            self
        }

        pub fn with_method(&mut self, meth: &MethodBuilder) -> &mut Self {
            if let Some(Delegate::MethodList(methods)) = &mut self.delegate {
                methods.push(meth.build().unwrap())
            } else {
                self.delegate = Some(Delegate::MethodList(vec![meth.build().unwrap()]))
            }
            self
        }
    }

    #[cfg(test)]
    impl MethodBuilder {
        pub fn ident(&mut self, name: impl IdentFragment) -> &mut Self {
            self.ident = Some(format_ident!("{}", name));
            self
        }

        pub fn rcv(&mut self) -> &mut Self {
            self.rcv = Some(syn::parse2(quote!(self)).unwrap());
            self
        }

        pub fn mut_rcv(&mut self) -> &mut Self {
            self.rcv = Some(syn::parse2(quote!(mut self)).unwrap());
            self
        }

        pub fn ref_rcv(&mut self) -> &mut Self {
            self.rcv = Some(syn::parse2(quote!(&self)).unwrap());
            self
        }

        pub fn ref_mut_rcv(&mut self) -> &mut Self {
            self.rcv = Some(syn::parse2(quote!(&mut self)).unwrap());
            self
        }

        pub fn with_arg(&mut self, arg: &str) -> &mut Self {
            if let FnArg::Typed(pt) = syn::parse_str::<FnArg>(arg).unwrap() {
                match &mut self.args {
                    None => self.args = Some(vec![pt]),
                    Some(args) => args.push(pt),
                };
            }
            self
        }

        pub fn ret(&mut self, ret: &str) -> &mut Self {
            self.ret = Some(syn::parse_str(ret).unwrap());
            self
        }
    }
}