magnus-macros 0.2.0

Derive and proc macros for magnus
Documentation
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, DeriveInput, Error, Meta};

use crate::util;

pub fn expand(attrs: TokenStream, item: TokenStream) -> TokenStream {
    quote! {
        #[derive(magnus::DataTypeFunctions, magnus::TypedData)]
        #[magnus(#attrs)]
        #item
    }
}

pub fn expand_derive_data_type_functions(input: DeriveInput) -> TokenStream {
    let ident = input.ident;
    let generics = input.generics;
    quote! {
        impl #generics magnus::DataTypeFunctions for #ident #generics {}
    }
}

pub fn expand_derive_typed_data(input: DeriveInput) -> TokenStream {
    if !input.generics.to_token_stream().is_empty() {
        return Error::new(
            input.generics.span(),
            "TypedData can't be derived for generic types",
        )
        .into_compile_error();
    }
    let mut attrs = input
        .attrs
        .clone()
        .into_iter()
        .filter(|attr| attr.path.is_ident("magnus"))
        .collect::<Vec<_>>();
    if attrs.len() > 1 {
        return attrs
            .into_iter()
            .map(|a| Error::new(a.span(), "duplicate attribute"))
            .reduce(|mut a, b| {
                a.combine(b);
                a
            })
            .unwrap()
            .into_compile_error();
    }
    if attrs.is_empty() {
        return Error::new(input.span(), "missing #[magnus] attribute").into_compile_error();
    }
    let attrs = match attrs.remove(0).parse_meta() {
        Ok(Meta::List(v)) => v.nested.into_iter().collect(),
        Ok(v) => return Error::new_spanned(v, "Expected meta list").into_compile_error(),
        Err(e) => return e.into_compile_error(),
    };
    let mut args = match util::Args::new_with_aliases(
        attrs,
        &[
            "class",
            "name",
            "mark",
            "size",
            "compact",
            "free_immediately",
            "wb_protected",
            "frozen_shareable",
        ],
        &vec![("free_immediatly", "free_immediately")]
            .into_iter()
            .collect(),
    ) {
        Ok(v) => v,
        Err(e) => return e.into_compile_error(),
    };

    let class = match args.extract::<String>("class") {
        Ok(v) => v,
        Err(e) => return e.into_compile_error(),
    };
    let name = match args.extract::<Option<String>>("name") {
        Ok(v) => v.unwrap_or_else(|| class.clone()),
        Err(e) => return e.into_compile_error(),
    };
    let mark = match args.extract::<Option<()>>("mark") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };
    let size = match args.extract::<Option<()>>("size") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };
    let compact = match args.extract::<Option<()>>("compact") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };
    let free_immediately = match args.extract::<Option<()>>("free_immediately") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };
    let wb_protected = match args.extract::<Option<()>>("wb_protected") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };
    let frozen_shareable = match args.extract::<Option<()>>("frozen_shareable") {
        Ok(v) => v.is_some(),
        Err(e) => return e.into_compile_error(),
    };

    let ident = input.ident;
    let mut builder = Vec::new();
    builder.push(quote! { let mut builder = magnus::DataType::builder::<Self>(#name); });
    if mark {
        builder.push(quote! { builder.mark(); });
    }
    if size {
        builder.push(quote! { builder.size(); });
    }
    if compact {
        builder.push(quote! { builder.compact(); });
    }
    if free_immediately {
        builder.push(quote! { builder.free_immediately(); });
    }
    if wb_protected {
        builder.push(quote! { builder.wb_protected(); });
    }
    if frozen_shareable {
        builder.push(quote! { builder.frozen_shareable(); });
    }
    builder.push(quote! { builder.build() });
    let builder = builder.into_iter().collect::<TokenStream>();
    let tokens = quote! {
        unsafe impl magnus::TypedData for #ident {
            fn class() -> magnus::RClass {
                use magnus::Module;
                *magnus::memoize!(magnus::RClass: magnus::RClass::default().funcall("const_get", (#class,)).unwrap())
            }

            fn data_type() -> &'static magnus::DataType {
                magnus::memoize!(magnus::DataType: {
                    #builder
                })
            }
        }
    };
    tokens
}