thin_delegate 0.2.0

Auto implementation of trivial delegation to inner types
Documentation
use proc_macro2::{Span, TokenStream};
use quote::TokenStreamExt;
use syn::parse::Parse;

mod kw {
    syn::custom_keyword!(delegate_fn_with_default_impl);
    syn::custom_keyword!(external_trait_def);
    syn::custom_keyword!(scheme);
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) struct FillDelegateArgs {
    pub delegate_fn_with_default_impl: bool,
    pub external_trait_def: Option<syn::Path>,
    pub scheme: Option<syn::ExprClosure>,
}

impl Default for FillDelegateArgs {
    fn default() -> Self {
        Self {
            delegate_fn_with_default_impl: true,
            external_trait_def: None,
            scheme: None,
        }
    }
}

impl FillDelegateArgs {
    pub fn validate(&self) -> syn::Result<()> {
        let Some(scheme) = self.scheme.as_ref() else {
            return Ok(());
        };

        if !scheme.attrs.is_empty() {
            // This shouldn't happen because parsing of `syn::ExprClosure` requires that the stream
            // starts with `|...|`.
            panic!();
        }

        if scheme.lifetimes.is_some() {
            return Err(syn::Error::new_spanned(
                &scheme.lifetimes,
                "scheme can't have lifetime",
            ));
        }

        if scheme.constness.is_some() {
            return Err(syn::Error::new_spanned(
                scheme.constness,
                "scheme can't have `const`",
            ));
        }

        if scheme.movability.is_some() {
            return Err(syn::Error::new_spanned(
                scheme.movability,
                "scheme can't have `static`",
            ));
        }

        if scheme.asyncness.is_some() {
            return Err(syn::Error::new_spanned(
                scheme.asyncness,
                "scheme can't have `async`",
            ));
        }

        if scheme.capture.is_some() {
            return Err(syn::Error::new_spanned(
                scheme.capture,
                "scheme can't have `move`",
            ));
        }

        if scheme.inputs.len() != 1 {
            return Err(syn::Error::new_spanned(
                &scheme.inputs,
                "scheme must have an arg without type",
            ));
        }

        let syn::Pat::Ident(arg) = &scheme.inputs[0] else {
            return Err(syn::Error::new_spanned(
                &scheme.inputs[0],
                "scheme must have an arg without type",
            ));
        };

        if !arg.attrs.is_empty() {
            return Err(syn::Error::new_spanned(
                arg,
                "arg of scheme can't have attributes",
            ));
        }

        if arg.by_ref.is_some() {
            return Err(syn::Error::new_spanned(
                arg.by_ref,
                "arg of scheme can't have `ref`",
            ));
        }

        if arg.mutability.is_some() {
            return Err(syn::Error::new_spanned(
                arg.mutability,
                "arg of scheme can't have `mut`",
            ));
        }

        if arg.subpat.is_some() {
            let subpat = arg.subpat.as_ref().unwrap();
            let mut spans = TokenStream::new();
            spans.append_all(Some(&subpat.0));
            spans.append_all(Some(&subpat.1));
            return Err(syn::Error::new_spanned(
                spans,
                "arg of scheme can't have `@ SUBPATTERN`",
            ));
        }

        Ok(())
    }

    pub fn scheme_arg_and_body(&self) -> Option<(&syn::Ident, &syn::Expr)> {
        let Some(scheme) = &self.scheme else {
            return None;
        };

        let syn::Pat::Ident(arg) = &scheme.inputs[0] else {
            panic!();
        };

        Some((&arg.ident, &scheme.body))
    }
}

impl Parse for FillDelegateArgs {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut this = FillDelegateArgs::default();

        let args =
            syn::punctuated::Punctuated::<ParsableArg, syn::Token![,]>::parse_terminated(input)?;
        for arg in args {
            match arg {
                ParsableArg::DelegateFnWithDefaultImpl {
                    delegate_fn_with_default_impl,
                    ..
                } => {
                    this.delegate_fn_with_default_impl = delegate_fn_with_default_impl.value;
                }
                ParsableArg::ExternalTraitDef { path, .. } => {
                    this.external_trait_def = Some(path);
                }
                ParsableArg::Scheme { closure, .. } => {
                    this.scheme = Some(closure);
                }
            }
        }

        Ok(this)
    }
}

#[derive(Debug)]
enum ParsableArg {
    DelegateFnWithDefaultImpl {
        #[allow(unused)]
        delegate_fn_with_default_impl_kw: kw::delegate_fn_with_default_impl,
        #[allow(unused)]
        eq_token: syn::Token![=],
        delegate_fn_with_default_impl: syn::LitBool,
    },
    ExternalTraitDef {
        #[allow(unused)]
        external_trait_def_kw: kw::external_trait_def,
        #[allow(unused)]
        eq_token: syn::Token![=],
        path: syn::Path,
    },
    Scheme {
        #[allow(unused)]
        scheme_kw: kw::scheme,
        #[allow(unused)]
        eq_token: syn::Token![=],
        closure: syn::ExprClosure,
    },
}

impl Parse for ParsableArg {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(kw::delegate_fn_with_default_impl) {
            Ok(ParsableArg::DelegateFnWithDefaultImpl {
                delegate_fn_with_default_impl_kw: input.parse()?,
                eq_token: input.parse()?,
                delegate_fn_with_default_impl: input.parse()?,
            })
        } else if lookahead.peek(kw::external_trait_def) {
            Ok(ParsableArg::ExternalTraitDef {
                external_trait_def_kw: input.parse()?,
                eq_token: input.parse()?,
                path: input.parse()?,
            })
        } else if lookahead.peek(kw::scheme) {
            Ok(ParsableArg::Scheme {
                scheme_kw: input.parse()?,
                eq_token: input.parse()?,
                closure: input.parse()?,
            })
        } else {
            Err(syn::Error::new(Span::call_site(), "error"))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use quote::quote;
    use syn::parse_quote;

    #[test]
    fn parsable() {
        let input = quote! {};
        let expected = FillDelegateArgs {
            delegate_fn_with_default_impl: true,
            external_trait_def: None,
            scheme: None,
        };
        assert_eq!(syn::parse2::<FillDelegateArgs>(input).unwrap(), expected);

        let input = quote! { external_trait_def = __external_trait_def };
        let expected = FillDelegateArgs {
            delegate_fn_with_default_impl: true,
            external_trait_def: Some(parse_quote! { __external_trait_def }),
            scheme: None,
        };
        assert_eq!(syn::parse2::<FillDelegateArgs>(input).unwrap(), expected);

        let input = quote! { delegate_fn_with_default_impl = false };
        let expected = FillDelegateArgs {
            delegate_fn_with_default_impl: false,
            external_trait_def: None,
            scheme: None,
        };
        assert_eq!(syn::parse2::<FillDelegateArgs>(input).unwrap(), expected);

        let input = quote! { scheme = |f| f(&self.0.key()) };
        let expected = FillDelegateArgs {
            delegate_fn_with_default_impl: true,
            external_trait_def: None,
            scheme: Some(parse_quote! { |f| f(&self.0.key()) }),
        };
        assert_eq!(syn::parse2::<FillDelegateArgs>(input).unwrap(), expected);

        let input =
            quote! { external_trait_def = __external_trait_def, scheme = |f| f(&self.0.key()) };
        let expected = FillDelegateArgs {
            delegate_fn_with_default_impl: true,
            external_trait_def: Some(parse_quote! { __external_trait_def }),
            scheme: Some(parse_quote! { |f| f(&self.0.key()) }),
        };
        assert_eq!(syn::parse2::<FillDelegateArgs>(input).unwrap(), expected);

        assert!(syn::parse2::<FillDelegateArgs>(quote! { hoge = hoge }).is_err());
        assert!(syn::parse2::<FillDelegateArgs>(quote! { external_trait_def }).is_err());
        assert!(syn::parse2::<FillDelegateArgs>(
            quote! { external_trait_def = __external_trait_def,, }
        )
        .is_err());
    }

    #[test]
    fn validate() {
        macro_rules! assert_validate_error {
            ($input:expr, $expected_msg:expr) => {
                assert_eq!(
                    syn::parse2::<FillDelegateArgs>($input)
                        .unwrap()
                        .validate()
                        .map_err(|e| format!("{e}")),
                    $expected_msg.map_err(|e| e.to_string())
                );
            };
        }

        assert_validate_error!(
            quote! { scheme = for<'a> |f| f(&self.key()) },
            Err("scheme can't have lifetime")
        );

        assert_validate_error!(
            quote! { scheme = const |f| f(&self.key()) },
            Err("scheme can't have `const`")
        );

        assert_validate_error!(
            quote! { scheme = static |f| f(&self.key()) },
            Err("scheme can't have `static`")
        );

        assert_validate_error!(
            quote! { scheme = async |f| f(&self.key()) },
            Err("scheme can't have `async`")
        );

        assert_validate_error!(
            quote! { scheme = move |f| f(&self.key()) },
            Err("scheme can't have `move`")
        );

        assert_validate_error!(
            quote! { scheme = || f(&self.key()) },
            Err("scheme must have an arg without type")
        );

        assert_validate_error!(
            quote! { scheme = |f, g| f(&self.key()) },
            Err("scheme must have an arg without type")
        );

        assert_validate_error!(
            quote! { scheme = |f: usize| f(&self.key()) },
            Err("scheme must have an arg without type")
        );

        assert_validate_error!(
            quote! { scheme = |#[cfg(all())] f| f(&self.key()) },
            Err("arg of scheme can't have attributes")
        );

        assert_validate_error!(
            quote! { scheme = |ref f| f(&self.key()) },
            Err("arg of scheme can't have `ref`")
        );

        assert_validate_error!(
            quote! { scheme = |mut f| f(&self.key()) },
            Err("arg of scheme can't have `mut`")
        );

        assert_validate_error!(
            quote! { scheme = |f @ Fuga| f(&self.key()) },
            Err("arg of scheme can't have `@ SUBPATTERN`")
        );
    }
}