magic-args-derive 0.1.3

Derive macros for magic-args.
Documentation
extern crate proc_macro;

use std::collections::HashSet;

use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::parse::{Parse, ParseStream};
use syn::{Data, DeriveInput, Index};

mod keyword {
    syn::custom_keyword!(skip);
}

enum MagicArgsAttribute {
    Skip,
}

impl Parse for MagicArgsAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        if lookahead.peek(keyword::skip) {
            let _skip: keyword::skip = input.parse()?;

            Ok(Self::Skip)
        } else {
            panic!("unknown attribute")
        }
    }
}

#[proc_macro_derive(MagicArgs, attributes(magic_args))]
pub fn magic_args_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item = syn::parse_macro_input!(input as DeriveInput);

    let Data::Struct(data) = item.data else {
        panic!("MagicArgs can only be derived on structs")
    };

    let item_name = item.ident.clone();
    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();

    let mut output = TokenStream::new();

    let mut types_seen = HashSet::new();

    for (idx, field) in data
        .fields
        .into_iter()
        .enumerate()
        .map(|(idx, field)| (Index::from(idx), field))
    {
        let mut skip = false;

        field
            .attrs
            .iter()
            .map(|attr| attr.parse_args().unwrap())
            .for_each(|attr: MagicArgsAttribute| match attr {
                MagicArgsAttribute::Skip => skip = true,
            });

        if skip {
            continue;
        }

        let field_type = field.ty;

        let field_accessor = match field.ident {
            Some(ident) => ident.to_token_stream(),
            None => idx.to_token_stream(),
        };

        output.extend(quote! {
            impl #impl_generics ::magic_args::Args<::magic_args::__private::Tagged<#field_type, #idx>> for #item_name #type_generics
                #where_clause
            {
                #[inline]
                fn get(&self) -> ::magic_args::__private::Tagged<#field_type, #idx> {
                    ::magic_args::__private::Tagged(::core::clone::Clone::clone(&self.#field_accessor))
                }
            }
        });

        if !types_seen.insert(field_type) {
            panic!("MagicArgs cannot contain two items of the same type");
        }
    }

    quote! {
        const _: () = {
            #output
        };
    }
    .into()
}