use quote::quote;
use syn::spanned::Spanned;
use syn::{FnArg, Ident, Pat, PatType, Signature, Type, TypePath};
pub(crate) struct ResourceInjection {
pub(crate) var_name: Ident,
pub(crate) full_type: Type,
pub(crate) inner_type: TypePath,
pub(crate) is_ref: bool,
pub(crate) is_mut: bool,
}
pub(crate) fn extract_args_info(
sig: &Signature,
) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> {
if sig.inputs.is_empty() {
return Err(syn::Error::new(
sig.span(),
"Function must have at least one parameter",
));
}
let first_arg = &sig.inputs[0];
let (prev_param, previous_type) = match first_arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
let param_pat = (**pat).clone();
match &**ty {
Type::Path(type_path) => {
if type_path.path.segments.len() > 1 {
return Err(syn::Error::new(
type_path.span(),
format!(
"The type `{}` must be a simple single-segment type, \
e.g. `Empty` instead of `other::Empty`. \
Qualified paths with `::` are not allowed here.",
quote! { #type_path }
),
));
}
(param_pat, type_path.clone())
}
Type::Reference(_) => {
return Err(syn::Error::new(
ty.span(),
"The first parameter (previous type) must be taken by move, \
not by reference. \
Use `prev: SomeEntry` instead of `prev: &SomeEntry`.",
));
}
_ => {
return Err(syn::Error::new(
ty.span(),
"First parameter type must be a type path",
));
}
}
}
FnArg::Receiver(_) => {
return Err(syn::Error::new(
first_arg.span(),
"Function cannot have self parameter",
));
}
};
let mut resources = Vec::new();
for arg in sig.inputs.iter().skip(1) {
match arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
let var_name = match &**pat {
Pat::Ident(pat_ident) => pat_ident.ident.clone(),
_ => {
return Err(syn::Error::new(
pat.span(),
"Resource injection parameter must be a simple identifier (e.g., `age: &Age`)",
));
}
};
let full_type = *(*ty).clone();
let (inner_type, is_ref, is_mut) = match &full_type {
Type::Reference(ref_type) => match &*ref_type.elem {
Type::Path(type_path) => {
let is_mut = ref_type.mutability.is_some();
(type_path.clone(), true, is_mut)
}
_ => {
return Err(syn::Error::new(
ty.span(),
"Reference resource type must be a type path (e.g., `age: &Age`)",
));
}
},
Type::Path(_) => {
return Err(syn::Error::new(
ty.span(),
"Resource injection parameter must be a reference (`&T` or `&mut T`), \
not an owned value. Use `age: &Age` instead of `age: Age`.",
));
}
_ => {
return Err(syn::Error::new(
ty.span(),
"Resource injection type must be a type path or reference to one \
(e.g., `age: Age` or `age: &Age`)",
));
}
};
resources.push(ResourceInjection {
var_name,
full_type,
inner_type,
is_ref,
is_mut,
});
}
FnArg::Receiver(_) => {
return Err(syn::Error::new(
arg.span(),
"Resource injection parameter cannot be self",
));
}
}
}
Ok((prev_param, previous_type, resources))
}
pub(crate) fn generate_immut_resource_bindings<'a>(
resources: impl Iterator<Item = &'a ResourceInjection>,
program_type: &proc_macro2::TokenStream,
) -> Vec<proc_macro2::TokenStream> {
resources
.filter(|r| !r.is_mut)
.map(|res| {
let var_binding_name = syn::Ident::new(
&format!("__{}_binding", &res.var_name.to_string()),
res.var_name.span(),
);
let var_name = &res.var_name;
let full_type = &res.full_type;
let inner_type = &res.inner_type;
if res.is_ref {
quote! {
let #var_binding_name = ::mingling::this::<#program_type>()
.res_or_default::<#inner_type>();
let #var_name: #full_type = #var_binding_name.as_ref();
}
} else {
quote! {
let #var_name: #full_type = ::mingling::this::<#program_type>()
.res_or_default::<#full_type>();
}
}
})
.collect()
}
pub(crate) fn wrap_body_with_mut_resources(
fn_body_stmts: &[syn::Stmt],
mut_resources: &[&ResourceInjection],
program_type: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let mut wrapped = quote! {
#(#fn_body_stmts)*
};
for res in mut_resources.iter() {
let var_name = &res.var_name;
let inner_type = &res.inner_type;
wrapped = quote! {
::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
#wrapped
}).into()
};
}
wrapped
}