|  | 
|  | 1 | +use crate::reducer::{assert_only_lifetime_generics, extract_typed_args}; | 
|  | 2 | +use crate::sym; | 
|  | 3 | +use crate::util::{check_duplicate, ident_to_litstr, match_meta}; | 
|  | 4 | +use proc_macro2::TokenStream; | 
|  | 5 | +use quote::quote; | 
|  | 6 | +use syn::parse::Parser as _; | 
|  | 7 | +use syn::{ItemFn, LitStr}; | 
|  | 8 | + | 
|  | 9 | +#[derive(Default)] | 
|  | 10 | +pub(crate) struct ProcedureArgs { | 
|  | 11 | +    /// For consistency with reducers: allow specifying a different export name than the Rust function name. | 
|  | 12 | +    name: Option<LitStr>, | 
|  | 13 | +} | 
|  | 14 | + | 
|  | 15 | +impl ProcedureArgs { | 
|  | 16 | +    pub(crate) fn parse(input: TokenStream) -> syn::Result<Self> { | 
|  | 17 | +        let mut args = Self::default(); | 
|  | 18 | +        syn::meta::parser(|meta| { | 
|  | 19 | +            match_meta!(match meta { | 
|  | 20 | +                sym::name => { | 
|  | 21 | +                    check_duplicate(&args.name, &meta)?; | 
|  | 22 | +                    args.name = Some(meta.value()?.parse()?); | 
|  | 23 | +                } | 
|  | 24 | +            }); | 
|  | 25 | +            Ok(()) | 
|  | 26 | +        }) | 
|  | 27 | +        .parse2(input)?; | 
|  | 28 | +        Ok(args) | 
|  | 29 | +    } | 
|  | 30 | +} | 
|  | 31 | + | 
|  | 32 | +pub(crate) fn procedure_impl(args: ProcedureArgs, original_function: &ItemFn) -> syn::Result<TokenStream> { | 
|  | 33 | +    let func_name = &original_function.sig.ident; | 
|  | 34 | +    let vis = &original_function.vis; | 
|  | 35 | + | 
|  | 36 | +    let procedure_name = args.name.unwrap_or_else(|| ident_to_litstr(func_name)); | 
|  | 37 | + | 
|  | 38 | +    assert_only_lifetime_generics(original_function, "procedures")?; | 
|  | 39 | + | 
|  | 40 | +    let typed_args = extract_typed_args(original_function)?; | 
|  | 41 | + | 
|  | 42 | +    // TODO: Require that procedures be `async` functions syntactically, | 
|  | 43 | +    // and use `futures_util::FutureExt::now_or_never` to poll them. | 
|  | 44 | +    // if !&original_function.sig.asyncness.is_some() { | 
|  | 45 | +    //     return Err(syn::Error::new_spanned( | 
|  | 46 | +    //         original_function.sig.clone(), | 
|  | 47 | +    //         "procedures must be `async`", | 
|  | 48 | +    //     )); | 
|  | 49 | +    // }; | 
|  | 50 | + | 
|  | 51 | +    // Extract all function parameter names. | 
|  | 52 | +    let opt_arg_names = typed_args.iter().map(|arg| { | 
|  | 53 | +        if let syn::Pat::Ident(i) = &*arg.pat { | 
|  | 54 | +            let name = i.ident.to_string(); | 
|  | 55 | +            quote!(Some(#name)) | 
|  | 56 | +        } else { | 
|  | 57 | +            quote!(None) | 
|  | 58 | +        } | 
|  | 59 | +    }); | 
|  | 60 | + | 
|  | 61 | +    let arg_tys = typed_args.iter().map(|arg| arg.ty.as_ref()).collect::<Vec<_>>(); | 
|  | 62 | +    let first_arg_ty = arg_tys.first().into_iter(); | 
|  | 63 | +    let rest_arg_tys = arg_tys.iter().skip(1); | 
|  | 64 | + | 
|  | 65 | +    // Extract the return type. | 
|  | 66 | +    let ret_ty_for_assert = match &original_function.sig.output { | 
|  | 67 | +        syn::ReturnType::Default => None, | 
|  | 68 | +        syn::ReturnType::Type(_, t) => Some(&**t), | 
|  | 69 | +    } | 
|  | 70 | +    .into_iter(); | 
|  | 71 | + | 
|  | 72 | +    let ret_ty_for_info = match &original_function.sig.output { | 
|  | 73 | +        syn::ReturnType::Default => quote!(()), | 
|  | 74 | +        syn::ReturnType::Type(_, t) => quote!(#t), | 
|  | 75 | +    }; | 
|  | 76 | + | 
|  | 77 | +    let register_describer_symbol = format!("__preinit__20_register_describer_{}", procedure_name.value()); | 
|  | 78 | + | 
|  | 79 | +    let lifetime_params = &original_function.sig.generics; | 
|  | 80 | +    let lifetime_where_clause = &lifetime_params.where_clause; | 
|  | 81 | + | 
|  | 82 | +    let generated_describe_function = quote! { | 
|  | 83 | +        #[export_name = #register_describer_symbol] | 
|  | 84 | +        pub extern "C" fn __register_describer() { | 
|  | 85 | +            spacetimedb::rt::register_procedure::<_, _, #func_name>(#func_name) | 
|  | 86 | +        } | 
|  | 87 | +    }; | 
|  | 88 | + | 
|  | 89 | +    Ok(quote! { | 
|  | 90 | +        const _: () = { | 
|  | 91 | +            #generated_describe_function | 
|  | 92 | +        }; | 
|  | 93 | +        #[allow(non_camel_case_types)] | 
|  | 94 | +        #vis struct #func_name { _never: ::core::convert::Infallible } | 
|  | 95 | +        const _: () = { | 
|  | 96 | +            fn _assert_args #lifetime_params () #lifetime_where_clause { | 
|  | 97 | +                #(let _ = <#first_arg_ty as spacetimedb::rt::ProcedureContextArg>::_ITEM;)* | 
|  | 98 | +                #(let _ = <#rest_arg_tys as spacetimedb::rt::ProcedureArg>::_ITEM;)* | 
|  | 99 | +                #(let _ = <#ret_ty_for_assert as spacetimedb::rt::IntoProcedureResult>::to_result;)* | 
|  | 100 | +            } | 
|  | 101 | +        }; | 
|  | 102 | +        impl #func_name { | 
|  | 103 | +            fn invoke(__ctx: spacetimedb::ProcedureContext, __args: &[u8]) -> spacetimedb::ProcedureResult { | 
|  | 104 | +                spacetimedb::rt::invoke_procedure(#func_name, __ctx, __args) | 
|  | 105 | +            } | 
|  | 106 | +        } | 
|  | 107 | +        #[automatically_derived] | 
|  | 108 | +        impl spacetimedb::rt::FnInfo for #func_name { | 
|  | 109 | +            /// The type of this function. | 
|  | 110 | +            type Invoke = spacetimedb::rt::ProcedureFn; | 
|  | 111 | + | 
|  | 112 | +            /// The function kind, which will cause scheduled tables to accept procedures. | 
|  | 113 | +            type FnKind = spacetimedb::rt::FnKindProcedure<#ret_ty_for_info>; | 
|  | 114 | + | 
|  | 115 | +            /// The name of this function | 
|  | 116 | +            const NAME: &'static str = #procedure_name; | 
|  | 117 | + | 
|  | 118 | +            /// The parameter names of this function | 
|  | 119 | +            const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*]; | 
|  | 120 | + | 
|  | 121 | +            /// The pointer for invoking this function | 
|  | 122 | +            const INVOKE: spacetimedb::rt::ProcedureFn = #func_name::invoke; | 
|  | 123 | + | 
|  | 124 | +            /// The return type of this function | 
|  | 125 | +            fn return_type(ts: &mut impl spacetimedb::sats::typespace::TypespaceBuilder) -> Option<spacetimedb::sats::AlgebraicType> { | 
|  | 126 | +                Some(<#ret_ty_for_info as spacetimedb::SpacetimeType>::make_type(ts)) | 
|  | 127 | +            } | 
|  | 128 | +        } | 
|  | 129 | +    }) | 
|  | 130 | +} | 
0 commit comments