ion_proc/function/
wrapper.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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(), &parameters, 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}