ctrlgen-impl 0.3.5

Generate enums for message-passing services
Documentation
use proc_macro2::TokenTree;
use syn::{parse_quote, Attribute};

use crate::{Argument, Method, Params};

use super::{InputData, ReceiverStyle};
impl InputData {
    pub fn parse_inherent_impl(item: &mut syn::ItemImpl, params: Params) -> syn::Result<InputData> {
        let returnval_mode = params.returnval.is_some();

        if let Some(x) = item.defaultness {
            return Err(syn::Error::new_spanned(x, "Default impls not supported"));
        }
        if let Some(x) = item.unsafety {
            return Err(syn::Error::new_spanned(
                x,
                "Handling `unsafe` is not implemented",
            ));
        }
        if let Some((_, path, _)) = &item.trait_ {
            return Err(syn::Error::new_spanned(
                path,
                "Trait impls are not supported, only inherent impls",
            ));
        }
        let generics = item.generics.clone();
        let (name, struct_args) = match &*item.self_ty {
            syn::Type::Path(p) => {
                if p.qself.is_some() {
                    return Err(syn::Error::new_spanned(
                        p,
                        "Impl has some tricky type. This is not supported",
                    ));
                }
                if p.path.segments.len() != 1 {
                    return Err(syn::Error::new_spanned(
                        p,
                        "Impl type must be a single ident with optional arguments",
                    ));
                }
                let segment = p.path.segments[0].clone();
                (segment.ident, segment.arguments)
            }
            _ => return Err(syn::Error::new_spanned(
                &*item.self_ty,
                "Type for `impl` should be a simple identifier without any paths or other tricks.",
            )),
        };

        let mut methods = Vec::with_capacity(item.items.len());

        for item in &mut item.items {
            if let syn::ImplItem::Method(method) = item {
                if method.defaultness.is_some() {
                    panic!("`default` not supported");
                }

                methods.push(parse_method(
                    &mut method.sig,
                    &mut method.attrs,
                    returnval_mode,
                    &params.context,
                )?);
            }
        }

        Ok(InputData {
            name,
            generics,
            struct_args,
            methods,
            params,
        })
    }
}

fn parse_method(
    method_signature: &mut syn::Signature,
    attrs: &mut Vec<syn::Attribute>,
    returnval_mode: bool,
    context: &Option<(syn::Ident, syn::Type)>,
) -> syn::Result<Method> {
    let mut enum_attr = vec![];
    let mut return_attr = vec![];
    let mut doc_attr = vec![];
    let r#async = method_signature.asyncness.is_some();
    if let Some(x) = method_signature.constness {
        return Err(syn::Error::new_spanned(x, "ctrlgen does not support const"));
    }
    if let Some(x) = method_signature.unsafety {
        return Err(syn::Error::new_spanned(
            x,
            "ctrlgen does not support unsafe",
        ));
    }
    if let Some(x) = &method_signature.abi {
        return Err(syn::Error::new_spanned(
            x,
            "ctrlgen does not support custom ABI in trait methods",
        ));
    }
    if !method_signature.generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            &method_signature.generics,
            "ctrlgen does not support generics or lifetimes in trait methods",
        ));
    }
    if let Some(x) = &method_signature.variadic {
        return Err(syn::Error::new_spanned(
            x,
            "ctrlgen does not support variadics",
        ));
    }
    if !returnval_mode && !matches!(method_signature.output, syn::ReturnType::Default) {
        return Err(syn::Error::new_spanned(
            &method_signature.output,
            "Specify `returnval` parameter to ctrlgen macro to handle methods with return types.",
        ));
    }
    for a in attrs.iter() {
        match a.path.get_ident() {
            Some(x) if x == "ctrlgen_enum_attr" || x == "ctrlgen_return_attr" => {
                let g = match a.tokens.clone().into_iter().next() {
                    Some(TokenTree::Group(g)) => g,
                    _ => {
                        return Err(syn::Error::new_spanned(
                            a,
                            "Input of `ctrlgen_{{enum|return}}_attr` should be single [...] group",
                        ));
                    }
                };
                let attr: Attribute = parse_quote! { # #g };
                match x {
                    x if x == "ctrlgen_enum_attr" => enum_attr.push(attr),
                    x if x == "ctrlgen_return_attr" => return_attr.push(attr),
                    _ => unreachable!(),
                }
            }
            Some(x) if x == "doc" => {
                doc_attr.push(a.clone());
            }
            _ => (),
        }
    }
    attrs.retain(|a| !matches!(a.path.get_ident(), Some(x) if x == "ctrlgen_enum_attr" || x == "ctrlgen_return_attr"));
    let mut args = Vec::with_capacity(method_signature.inputs.len());
    let mut receiver_style = None;
    let ret = match &method_signature.output {
        syn::ReturnType::Default => None,
        syn::ReturnType::Type(_, t) => Some(*t.clone()),
    };
    let mut is_first_arg = true;
    for input_args in &mut method_signature.inputs {
        match input_args {
            syn::FnArg::Receiver(r) => {
                receiver_style = if let Some(rr) = &r.reference {
                    if let Some(x) = &rr.1 {
                        return Err(syn::Error::new_spanned(
                            x,
                            "ctrlgen does not support explicit lifetimes",
                        ));
                    }
                    if r.mutability.is_some() {
                        Some(ReceiverStyle::Mut)
                    } else {
                        Some(ReceiverStyle::Ref)
                    }
                } else {
                    Some(ReceiverStyle::Move)
                }
            }
            syn::FnArg::Typed(arg) => {
                if is_first_arg {
                    is_first_arg = false;
                    if let Some((ctx_name, _ctx_ty)) = context {
                        if let syn::Pat::Ident(pat) = &*arg.pat {
                            let arg_name = &pat.ident;
                            if *arg_name != ctx_name.to_string() {
                                return Err(syn::Error::new_spanned(&arg, format!("Expected first argument to be the context {ctx_name}. Got {arg_name}")));
                            }
                        }
                        // We're good, skip this argument.
                        continue;
                    }
                }

                let mut enum_attr = vec![];
                let mut to_owned = false;
                for a in attrs.iter() {
                    match a.path.get_ident() {
                        Some(x) if x == "ctrlgen_enum_attr" => {
                            match a.tokens.clone().into_iter().next() {
                                Some(TokenTree::Group(g)) => {
                                    enum_attr.push(g);
                                }
                                _ => return Err(syn::Error::new_spanned(
                                    a,
                                    "Input of `ctrlgen_enum_attr` should be a single [...] group",
                                )),
                            }
                        }
                        Some(x) if x == "ctrlgen_to_owned" => {
                            if !a.tokens.is_empty() {
                                return Err(syn::Error::new_spanned(
                                    a,
                                    "`ctrlgen_to_owned` does not accept any additional arguments",
                                ));
                            }
                            to_owned = true;
                        }
                        _ => (),
                    }
                }
                arg.attrs.retain(|a| match a.path.get_ident() {
                    Some(x) if x == "ctrlgen_enum_attr" => false,
                    Some(x) if x == "ctrlgen_to_owned" => false,
                    _ => true,
                });
                match &*arg.pat {
                    syn::Pat::Ident(pi) => {
                        if pi.by_ref.is_some() {
                            return Err(syn::Error::new_spanned(pi, "ctrlgen does not support `ref` in argument names"));
                        }
                        if returnval_mode && pi.ident == "ret" {
                            return Err(syn::Error::new_spanned(&pi.ident, format!("In `returnval` mode, method's arguments cannot be named literally `ret`. Rename it away in `{}`.", method_signature.ident)));
                        }
                        args.push(Argument { name: pi.ident.clone(), ty: *arg.ty.clone(), enum_attr, to_owned });
                    }
                    _ => return Err(syn::Error::new_spanned(arg, "ctrlgen does not support method arguments that are patterns, not just simple identifiers")),
                }
            }
        }
    }
    if receiver_style.is_none() {
        return Err(syn::Error::new_spanned(
            method_signature,
            "ctrlgen does not support methods that do not accept `self`",
        ));
    }
    if is_first_arg && context.is_some() {
        return Err(syn::Error::new_spanned(
            &method_signature,
            "Every method needs to take the context arg when specified",
        ));
    }
    Ok(Method {
        args,
        name: method_signature.ident.clone(),
        receiver_style: receiver_style.unwrap(),
        ret,
        enum_attr,
        return_attr,
        doc_attr,
        r#async,
    })
}