spire_enum_macros 1.2.0

Procedural macros distributed by the crate `spire_enum`.
Documentation
use super::*;

#[derive(Parse, ToTokens)]
pub enum InputImplItem {
    Const(Box<InputImplItemConst>),
    Fn(Box<InputImplItemFn>),
    Type(Box<InputImplItemAssocType>),
    Macro(Box<syn::ImplItemMacro>),
}

#[derive(Parse, ToTokens)]
pub struct InputImplItemFn {
    pub attrs: Any<Attribute<SynMeta>>,
    pub vis: Visibility,
    pub sig: InputFnSignature,
    pub body: InputImplItemFnBody,
}

#[derive(Parse, ToTokens)]
pub enum InputImplItemFnBody {
    Block(Block),
    SemiColon(Token![;]),
}

#[derive(Parse, ToTokens)]
pub struct InputImplItemConst {
    pub attrs: Any<Attribute<SynMeta>>,
    pub vis: Visibility,
    pub const_token: Token![const],
    pub ident: Ident,
    pub generics: Optional<InputGenerics>,
    pub colon_token: Token![:],
    pub ty: Type,
    pub body: InputImplItemConstBody,
    pub semi_token: Token![;],
}

#[derive(Parse, ToTokens)]
pub enum InputImplItemConstBody {
    Some(Token![=], Box<Expr>),
    None,
}

#[derive(Parse, ToTokens)]
pub struct InputImplItemAssocType {
    pub attrs: Any<Attribute<SynMeta>>,
    pub type_token: Token![type],
    pub ident: Ident,
    pub generics: Optional<InputGenerics>,
    pub body: InputImplItemAssocTypeBody,
    pub semi_token: Token![;],
}

#[derive(Parse, ToTokens)]
pub enum InputImplItemAssocTypeBody {
    Some(Token![=], Box<Type>),
    None,
}

#[derive(Parse, ToTokens)]
pub struct InputFnSignature {
    pub constness: Optional<Token![const]>,
    pub asyncness: Optional<Token![async]>,
    pub unsafety: Optional<Token![unsafe]>,
    pub abi: Optional<syn::Abi>,
    pub fn_token: Token![fn],
    pub ident: Ident,
    pub generics: Optional<InputGenerics>,
    pub inputs: Paren<InputPunctuated<FnArg, Token![,]>>,
    pub output: syn::ReturnType,
    pub where_clause: Optional<WhereClause>,
}

pub fn find_enum_ident(ty: &Type) -> Option<&Ident> {
    match ty {
        | Type::Group(TypeGroup { elem, .. })
        | Type::Paren(TypeParen { elem, .. })
        | Type::Reference(TypeReference { elem, .. })
        | Type::Ptr(TypePtr { elem, .. }) => find_enum_ident(elem),

        Type::Tuple(TypeTuple { elems, .. }) => elems.first().and_then(find_enum_ident),
        Type::Path(TypePath { path, .. }) => path.segments.last().map(|seg| &seg.ident),
        _ => None,
    }
}

pub struct SaneMethod {
    pub attrs: Any<Attribute<SynMeta>>,
    pub vis: Visibility,
    pub sig: SaneMethodSignature,
    pub _semi_token: Token![;],
}

pub struct SaneMethodSignature {
    pub constness: Optional<Token![const]>,
    pub asyncness: Optional<Token![async]>,
    pub unsafety: Optional<Token![unsafe]>,
    pub abi: Optional<syn::Abi>,
    pub fn_token: Token![fn],
    pub ident: Ident,
    pub generics: Optional<InputGenerics>,
    pub paren_token: syn::token::Paren,
    pub receiver: ReceiverKind,
    pub other_inputs: Vec<SaneNonReceiverFnArg>,
    pub output: syn::ReturnType,
    pub where_clause: Optional<WhereClause>,
}

pub enum ReceiverKind {
    Std(Receiver),
    NonReceiverWithAttr {
        receiver_attr: Attribute<kw_receiver>,
        arg: SaneNonReceiverFnArg,
        position: usize,
    },
}

pub fn sanitize_method_signature(input: InputFnSignature) -> Result<SaneMethodSignature> {
    let InputFnSignature {
        constness,
        asyncness,
        unsafety,
        abi,
        fn_token,
        ident,
        generics,
        inputs,
        output,
        where_clause,
    } = input;

    let (paren_token, inputs) = inputs.into_parts();

    let mut receiver_opt: Option<ReceiverKind> = None;
    let mut other_inputs = Vec::new();

    for (position, arg) in inputs.inner.into_iter().enumerate() {
        match (arg, &receiver_opt) {
            (FnArg::Receiver(second_receiver), Some(first_receiver)) => {
                bail!(second_receiver => "Expected exactly one receiver.",
                    first_receiver => "First receiver declared here"
                );
            }
            (FnArg::Receiver(first_receiver), None) => {
                receiver_opt = Some(ReceiverKind::Std(first_receiver));
            }
            (FnArg::Typed(mut typed_arg), Some(first_receiver)) => {
                if extract_attr::<kw_receiver>(&mut typed_arg.attrs).is_some() {
                    bail!(typed_arg => "Expected exactly one receiver.",
                        first_receiver => "First receiver declared here"
                    );
                } else {
                    let sanitized = sanitize_fn_arg(typed_arg)?;
                    other_inputs.push(sanitized);
                }
            }
            (FnArg::Typed(mut typed_arg), None) => {
                if let Some(receiver_attr) = extract_attr::<kw_receiver>(&mut typed_arg.attrs) {
                    let sanitized = sanitize_fn_arg(typed_arg)?;
                    receiver_opt = Some(ReceiverKind::NonReceiverWithAttr {
                        receiver_attr,
                        arg: sanitized,
                        position,
                    });
                } else {
                    let sanitized = sanitize_fn_arg(typed_arg)?;
                    other_inputs.push(sanitized);
                }
            }
        }
    }

    let Some(receiver) = receiver_opt else {
        bail!(ident => "Expected function to have a receiver.\n\
			Help: To delegate the implementation to the variants, we need `Self`(the enum) as an argument.")
    };

    Ok(SaneMethodSignature {
        constness,
        asyncness,
        unsafety,
        abi,
        fn_token,
        ident,
        generics,
        paren_token,
        receiver,
        other_inputs,
        output,
        where_clause,
    })
}

impl ReceiverKind {
    pub fn span(&self) -> Span {
        match self {
            ReceiverKind::Std(std) => std.span(),
            ReceiverKind::NonReceiverWithAttr {
                receiver_attr,
                arg: _,
                position: _,
            } => receiver_attr.span(),
        }
    }
}

pub struct SaneNonReceiverFnArg {
    pub attrs: Vec<SynAttribute>,
    pub pat_ident: PatIdent,
    pub colon_token: Token![:],
    pub ty: Box<Type>,
}

impl ToTokens for SaneNonReceiverFnArg {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let SaneNonReceiverFnArg {
            attrs,
            pat_ident,
            colon_token,
            ty,
        } = self;
        tokens.extend(quote! { #(#attrs)* #pat_ident #colon_token #ty });
    }
}

pub fn sanitize_fn_arg(arg: PatType) -> Result<SaneNonReceiverFnArg> {
    let PatType {
        attrs,
        pat,
        colon_token,
        ty,
    } = arg;

    match *pat {
        Pat::Ident(pat_ident) => Ok(SaneNonReceiverFnArg {
            attrs,
            pat_ident,
            colon_token,
            ty,
        }),
        other => {
            bail!(other => "Patterns in parameters aren't allowed, \
				please use a plain identifier (e.g: `foo: Ty`).")
        }
    }
}

pub fn sane_method_output(method: SaneMethod, macro_ident: &Ident) -> Result<TokenStream> {
    let SaneMethod {
        attrs,
        vis,
        sig,
        _semi_token: _,
    } = method;

    let SaneMethodSignature {
        constness,
        asyncness,
        unsafety: fn_unsafety,
        abi,
        fn_token,
        ident: fn_ident,
        generics: fn_generics,
        paren_token,
        receiver,
        other_inputs,
        output,
        where_clause: fn_where_clause,
    } = sig;

    let attrs = attrs.iter();
    let maybe_await = asyncness.as_ref().map(|_| quote! { . await });

    match receiver {
        ReceiverKind::Std(std) => {
            let other_params_tt = other_inputs
                .iter()
                .map(|other_param| quote! { #other_param });

            let invocation_args = other_inputs
                .iter()
                .map(|other_arg| &other_arg.pat_ident.ident);

            let all_args = quote! { #std, #(#other_params_tt),* };
            let inputs = Paren::from((paren_token, all_args));

            Ok(quote! {
                #( #attrs )*
                #vis #constness #asyncness #fn_unsafety #abi #fn_token
                #fn_ident #fn_generics #inputs #output #fn_where_clause {
                    #macro_ident ! { self => |__this| __this.#fn_ident( #(#invocation_args),* ) #maybe_await .into() }
                }
            })
        }
        ReceiverKind::NonReceiverWithAttr {
            receiver_attr: _,
            arg: receiver_arg,
            position,
        } => {
            let mut params_tt = other_inputs
                .iter()
                .map(|other_param| quote! { #other_param })
                .collect::<Vec<_>>();
            params_tt.insert(position, quote! { #receiver_arg });
            let params_punctuated = quote! { #(#params_tt),* };
            let inputs = Paren::from((paren_token, params_punctuated));
            let receiver_ident = &receiver_arg.pat_ident.ident;

            let mut invocation_args = other_inputs
                .iter()
                .map(|arg| &arg.pat_ident.ident)
                .collect::<Vec<_>>();
            let invocation_receiver_ident: Ident = parse_quote! { _this };
            invocation_args.insert(position, &invocation_receiver_ident);

            Ok(quote! {
                #( #attrs )*
                #vis #constness #asyncness #fn_unsafety #abi #fn_token
                #fn_ident #fn_generics #inputs #output #fn_where_clause {
                    #macro_ident ! {
                        @NON_RECEIVER
                        { #fn_ident } { #receiver_ident => |#invocation_receiver_ident| } { #(#invocation_args),* }
                        #maybe_await .into()
                    }
                }
            })
        }
    }
}