tarantool-proc 4.1.0

Tarantool proc macros
Documentation
use quote::quote;

use crate::default_tarantool_crate_path;

macro_rules! unwrap_or_compile_error {
    ($expr:expr) => {
        match $expr {
            Ok(v) => v,
            Err(e) => {
                return e.to_compile_error().into();
            }
        }
    };
}

pub fn impl_macro_attribute(
    attr: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let fn_item = syn::parse_macro_input!(item as syn::ItemFn);
    let ctx = unwrap_or_compile_error!(Context::from_args(attr.into()));
    let fn_name = &fn_item.sig.ident;
    let test_name = fn_name.to_string();
    let unique_name = format!("TARANTOOL_MODULE_TEST_CASE_{}", test_name.to_uppercase());
    let test_name_ident = syn::Ident::new(&unique_name, fn_name.span());
    let Context {
        tarantool,
        section,
        linkme,
        should_panic,
    } = ctx;

    let fn_item = if fn_item.sig.asyncness.is_some() {
        let body = fn_item.block;
        quote! {
            fn #fn_name() {
                #tarantool::fiber::block_on(async { #body })
            }
        }
    } else {
        quote! {
            #fn_item
        }
    };

    quote! {
        #[#linkme::distributed_slice(#section)]
        #[linkme(crate = #linkme)]
        #[used]
        static #test_name_ident: #tarantool::test::TestCase = #tarantool::test::TestCase::new(
            ::std::concat!(::std::module_path!(), "::", #test_name),
            #fn_name,
            #should_panic,
        );

        #fn_item
    }
    .into()
}

#[derive(Debug)]
struct Context {
    tarantool: syn::Path,
    section: syn::Path,
    linkme: syn::Path,
    should_panic: syn::Expr,
}

impl Context {
    fn from_args(tokens: proc_macro2::TokenStream) -> Result<Self, syn::Error> {
        let mut tarantool = default_tarantool_crate_path();
        let mut linkme = None;
        let mut section = None;
        let mut should_panic = syn::parse_quote! { false };

        syn::parse::Parser::parse2(
            |input: syn::parse::ParseStream| -> Result<(), syn::Error> {
                while !input.is_empty() {
                    let ident: syn::Ident = input.parse()?;
                    if ident == "tarantool" {
                        input.parse::<syn::Token![=]>()?;
                        let value: syn::LitStr = input.parse()?;
                        tarantool = value.parse()?;
                    } else if ident == "linkme" {
                        input.parse::<syn::Token![=]>()?;
                        let value: syn::LitStr = input.parse()?;
                        linkme = Some(value.parse()?);
                    } else if ident == "section" {
                        input.parse::<syn::Token![=]>()?;
                        let value: syn::LitStr = input.parse()?;
                        section = Some(value.parse()?);
                    } else if ident == "should_panic" {
                        if input.parse::<syn::Token![=]>().is_ok() {
                            should_panic = input.parse()?;
                        } else {
                            should_panic = syn::parse_quote! { true };
                        }
                    } else {
                        return Err(syn::Error::new(
                            ident.span(),
                            format!("unknown argument `{ident}`, expected one of `tarantool`, `linkme`, `section`, `should_panic`"),
                        ));
                    }

                    if !input.is_empty() {
                        input.parse::<syn::Token![,]>()?;
                    }
                }

                Ok(())
            },
            tokens,
        )?;

        let section = section
            .unwrap_or_else(|| syn::parse_quote! { #tarantool::test::TARANTOOL_MODULE_TESTS });

        let linkme = linkme.unwrap_or_else(|| syn::parse_quote! { #tarantool::linkme });

        Ok(Self {
            tarantool,
            section,
            linkme,
            should_panic,
        })
    }
}