forward-methods 0.0.2

A derive macro for forwarding methods from composed objects
Documentation
use proc_macro2::{Ident, Span};
use syn::{
    parenthesized,
    parse::{Parse, ParseStream},
    Error, FnArg, PatType, Receiver, Result, Token,
};

use crate::model::Method;

impl Parse for Method {
    fn parse(input: ParseStream) -> Result<Self> {
        _ = input.parse::<Token![fn]>()?;

        let ident: Ident = input.parse()?;
        let (rcv, args) = parse_fn_args(input)?;
        let ret = input.parse()?;

        Ok(Method {
            ident,
            rcv,
            args,
            ret,
        })
    }
}

fn parse_fn_args(input: ParseStream) -> Result<(Receiver, Vec<PatType>)> {
    let args_buf;
    _ = parenthesized!(args_buf in input);

    let punct_args = args_buf.parse_terminated(FnArg::parse, Token![, ])?;
    let mut args = punct_args.iter();

    Ok((
        get_receiver(args.next())?,
        args.flat_map(select_pat_type).collect(),
    ))
}

fn get_receiver(arg: Option<&FnArg>) -> Result<Receiver> {
    if let Some(FnArg::Receiver(rcv)) = arg {
        Ok(rcv.clone())
    } else {
        Err(Error::new(
            Span::call_site(),
            "method must have a receiver to be forwarded",
        ))
    }
}

fn select_pat_type(arg: &FnArg) -> Option<PatType> {
    if let FnArg::Typed(pt) = arg.clone() {
        Some(pt)
    } else {
        None
    }
}

#[cfg(test)]
mod test {
    use proc_macro2::TokenStream;
    use quote::quote;
    use test_case::test_case;

    use crate::model::{Method, MethodBuilder};

    #[test_case(
        quote!(fn test(self)),
        MethodBuilder::default().ident("test").rcv();
        "should parse method with move receiver"
    )]
    #[test_case(
        quote!(fn test(mut self)),
        MethodBuilder::default().ident("test").mut_rcv();
        "should parse method with mut receiver"
    )]
    #[test_case(
        quote!(fn test(&self)),
        MethodBuilder::default().ident("test").ref_rcv();
        "should parse method with ref receiver"
    )]
    #[test_case(
        quote!(fn test(&mut self)),
        MethodBuilder::default().ident("test").ref_mut_rcv();
        "should parse method with mut ref receiver"
    )]
    #[test_case(
        quote!(fn test(self, val: String)),
        MethodBuilder::default().ident("test").rcv().with_arg("val: String");
        "should parse method with multiple params"
    )]
    #[test_case(
        quote!(fn test(self) -> Result<String, Error>),
        MethodBuilder::default().ident("test").rcv().ret("-> Result<String, Error>");
        "should parse method with return value"
    )]
    #[test_case(
        quote!(fn test(self) -> (String, uint)),
        MethodBuilder::default().ident("test").rcv().ret("-> (String, uint)");
        "should parse method with tuple return value"
    )]
    fn should_parse_method(input: TokenStream, want: &mut MethodBuilder) {
        let meth = syn::parse2::<Method>(input).unwrap();

        assert_eq!(meth, want.build().unwrap())
    }

    #[test_case(quote!(fn test()), "method must have a receiver to be forwarded"; "should require a receiver")]
    fn should_fail_to_parse_method(input: TokenStream, want: &str) {
        let err = syn::parse2::<Method>(input).unwrap_err();

        assert_eq!(err.to_string(), want)
    }
}