Skip to main content

miniextendr_macros/
call_method_def.rs

1//! Shared `.Call` registration for generated SEXP wrappers.
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6/// Emit a registration with exactly the wrapper's C arity. The caller owns
7/// cfg/doc attributes and decides whether its signature includes a call slot.
8/// The registered name is also the linker symbol used by the wasm writer.
9pub(crate) fn call_method_def(
10    def_ident: &syn::Ident,
11    c_ident: &syn::Ident,
12    num_args: usize,
13) -> TokenStream {
14    let c_name = syn::LitCStr::new(
15        std::ffi::CString::new(crate::naming::ident_name(c_ident))
16            .expect("valid C symbol")
17            .as_c_str(),
18        c_ident.span(),
19    );
20    let arity = syn::LitInt::new(&num_args.to_string(), c_ident.span());
21    let params = std::iter::repeat_n({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "SEXP");
    _s
}quote!(::miniextendr_api::SEXP), num_args);
22    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "cfg_attr");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "not");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "target_arch");
                            ::quote::__private::push_eq(&mut _s);
                            ::quote::__private::parse(&mut _s, "\"wasm32\"");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "linkme");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s,
                        "distributed_slice");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "registry");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "MX_CALL_DEFS");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "linkme");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "crate");
                            ::quote::__private::push_eq(&mut _s);
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "linkme");
                            _s
                        });
                    _s
                });
            _s
        });
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s,
                        "non_upper_case_globals");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "non_snake_case");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::ToTokens::to_tokens(&def_ident, &mut _s);
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "sys");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "R_CallMethodDef");
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "sys");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "R_CallMethodDef");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "name");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&c_name, &mut _s);
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "as_ptr");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "fun");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Some");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "unsafe");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Brace,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "core");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "mem");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "transmute");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_lt(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "unsafe");
                            ::quote::__private::push_ident(&mut _s, "extern");
                            ::quote::__private::parse(&mut _s, "\"C-unwind\"");
                            ::quote::__private::push_ident(&mut _s, "fn");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    {
                                        use ::quote::__private::ext::*;
                                        let mut _first = true;
                                        let has_iter = ::quote::__private::HasIterator::<false>;
                                        #[allow(unused_mut)]
                                        let (mut params, i) = params.quote_into_iter();
                                        let has_iter = has_iter | i;
                                        <_ as
                                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                                        while true {
                                            let params =
                                                match params.next() {
                                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                                    None => break,
                                                };
                                            if !_first { ::quote::__private::push_comma(&mut _s); }
                                            _first = false;
                                            ::quote::ToTokens::to_tokens(&params, &mut _s);
                                        }
                                    }
                                    _s
                                });
                            ::quote::__private::push_rarrow(&mut _s);
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "SEXP");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "unsafe");
                            ::quote::__private::push_ident(&mut _s, "extern");
                            ::quote::__private::parse(&mut _s, "\"C-unwind\"");
                            ::quote::__private::push_ident(&mut _s, "fn");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                ::quote::__private::TokenStream::new());
                            ::quote::__private::push_rarrow(&mut _s);
                            ::quote::__private::push_star(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "mut");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "core");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "ffi");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "c_void");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_gt(&mut _s);
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::ToTokens::to_tokens(&c_ident, &mut _s);
                                    _s
                                });
                            _s
                        });
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "numArgs");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&arity, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
23        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_CALL_DEFS), linkme(crate = ::miniextendr_api::linkme))]
24        #[allow(non_upper_case_globals, non_snake_case)]
25        static #def_ident: ::miniextendr_api::sys::R_CallMethodDef =
26            ::miniextendr_api::sys::R_CallMethodDef {
27                name: #c_name.as_ptr(),
28                fun: ::core::option::Option::Some(unsafe {
29                    ::core::mem::transmute::<
30                        unsafe extern "C-unwind" fn(#(#params),*) -> ::miniextendr_api::SEXP,
31                        unsafe extern "C-unwind" fn() -> *mut ::core::ffi::c_void,
32                    >(#c_ident)
33                }),
34                numArgs: #arity,
35            };
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::call_method_def;
42
43    #[test]
44    fn registers_the_linker_symbol_with_the_given_arity() {
45        let def: syn::Ident = syn::parse_quote!(__MX_CALL_DEF_RDATA_SET_Engine_rate);
46        let c_ident: syn::Ident = syn::parse_quote!(C_Engine__set_rate);
47        let code = call_method_def(&def, &c_ident, 3).to_string();
48        assert!(
49            code.contains(r#"name : c"C_Engine__set_rate" . as_ptr ()"#),
50            "{code}"
51        );
52        assert!(code.contains("numArgs : 3"), "{code}");
53        // The transmute names the arity in its source type, so a wrapper with
54        // another parameter count fails to compile instead of registering.
55        assert!(
56            code.contains(
57                "fn (:: miniextendr_api :: SEXP , :: miniextendr_api :: SEXP , :: miniextendr_api :: SEXP) -> :: miniextendr_api :: SEXP"
58            ),
59            "{code}"
60        );
61        assert!(code.contains("MX_CALL_DEFS"), "{code}");
62    }
63}