ion_proc/function/
wrapper.rs1use proc_macro2::TokenStream;
8use quote::{ToTokens, format_ident, quote, quote_spanned};
9use syn::punctuated::Punctuated;
10use syn::spanned::Spanned as _;
11use syn::{Error, FnArg, GenericParam, ItemFn, Result, ReturnType, Type, parse_quote, parse2};
12
13use crate::function::inner::impl_inner_fn;
14use crate::function::parameter::Parameters;
15use crate::utils::{new_token, path_ends_with};
16
17pub(crate) fn impl_wrapper_fn(
18 ion: &TokenStream, mut function: ItemFn, class_ty: Option<&Type>, is_constructor: bool, error_key: &str,
19) -> Result<(ItemFn, Parameters)> {
20 if function.sig.asyncness.is_some() {
21 return Err(Error::new(
22 function.sig.asyncness.span(),
23 "Async functions cannot be used as methods. Use `Promise::block_on_future` or `future_to_promise` instead.",
24 ));
25 }
26
27 let parameters = Parameters::parse(&function.sig.inputs, class_ty)?;
28 let (statements, idents) = parameters.to_statements(ion)?;
29
30 let inner = impl_inner_fn(function.clone(), ¶meters, class_ty.is_none());
31
32 let wrapper_generics: [GenericParam; 2] = [parse_quote!('cx), parse_quote!('a)];
33 let mut wrapper_args: Vec<FnArg> = vec![
34 parse_quote!(__cx: &'cx #ion::Context),
35 parse_quote!(__args: &'a mut #ion::Arguments<'cx>),
36 ];
37
38 let nargs = parameters.nargs;
39
40 let mut this_statements = parameters.to_this_statement(ion, class_ty.is_some())?.map(ToTokens::into_token_stream);
41 if is_constructor {
42 wrapper_args.push(parse_quote!(__this: &mut #ion::Object<'cx>));
43 } else {
44 this_statements = this_statements.map(|statement| {
45 quote!(
46 let __this = &mut __accessor.this().to_object(__cx);
47 #statement
48 )
49 });
50 }
51
52 let output = match &function.sig.output {
53 ReturnType::Default => parse_quote!(()),
54 ReturnType::Type(_, ty) => *ty.clone(),
55 };
56
57 let result = if let Type::Path(ty) = &output {
58 if path_ends_with(&ty.path, "Result") || path_ends_with(&ty.path, "ResultExc") {
59 quote!(__result.map_err(::std::convert::Into::into))
60 } else {
61 quote!(#ion::ResultExc::<#ty>::Ok(__result))
62 }
63 } else {
64 quote!(#ion::ResultExc::<#output>::Ok(__result))
65 };
66 let result = quote!(#result.map(Box::new));
67 let result = if !is_constructor {
68 quote!(#result.map(|__result| #ion::conversions::IntoValue::into_value(__result, __cx, &mut __args.rval())))
69 } else {
70 quote!(#result.map(|__result| #ion::ClassDefinition::set_private(__this.handle().get(), __result)))
71 };
72
73 let wrapper_inner = class_ty.is_none().then_some(&inner);
74
75 let ident = &function.sig.ident;
76 let call = if let Some(class) = class_ty {
77 quote!(#class::#ident)
78 } else {
79 quote!(inner)
80 };
81
82 let this_ident = parameters.get_this_ident();
83 let inner_call = if this_ident.is_some() && this_ident.unwrap() == "self" {
84 quote!(#call(self_, #(#idents,)*))
85 } else {
86 quote!(#call(#(#idents,)*))
87 };
88
89 let body = parse2(quote_spanned!(function.span() => {
90 __args.check_args(__cx, #nargs, ::std::option::Option::Some(#error_key))?;
91
92 let mut __accessor = __args.access();
93 #this_statements
94 #(#statements)*
95
96 #wrapper_inner
97
98 #[allow(clippy::let_unit_value, clippy::redundant_type_annotations, clippy::used_underscore_binding)]
99 let __result: #output = #inner_call;
100 #result
101 }))?;
102
103 function.sig.unsafety = Some(new_token![unsafe]);
104 function.sig.ident = format_ident!("wrapper", span = function.sig.ident.span());
105 function.sig.inputs = Punctuated::from_iter(wrapper_args);
106 function.sig.generics.params = Punctuated::from_iter(wrapper_generics);
107 function.sig.output = parse_quote!(-> #ion::ResultExc<()>);
108
109 function.attrs.clear();
110 function.block = body;
111
112 Ok((function, parameters))
113}