use proc_macro::TokenStream as TokenStream1;
use quote::quote;
use syn::{FnArg, GenericArgument, GenericParam, ItemFn, PathArguments, ReturnType, Type};
pub fn installs_impl(item: TokenStream1) -> TokenStream1 {
let function = syn::parse_macro_input!(item as ItemFn);
let Some(installed) = installed(&function) else {
return syn::Error::new_spanned(
&function.sig,
"#[installs] reads the feature from what the function returns - write \
`-> anyhow::Result<YourFeature>`",
)
.to_compile_error()
.into();
};
let gc = crate::handler::guinea_core_crate_path();
let feature = crate::segment::context_path();
let name = &function.sig.ident;
let (impl_generics, _, where_clause) = function.sig.generics.split_for_impl();
let turbofish: Vec<_> = function
.sig
.generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(ty) => Some(&ty.ident),
_ => None,
})
.collect();
let called = if turbofish.is_empty() {
quote!(#name)
} else {
quote!(#name::<#(#turbofish),*>)
};
let (params, call) = match function.sig.inputs.iter().nth(1) {
Some(FnArg::Typed(second)) => {
let params = match &*second.ty {
Type::Reference(reference) => (*reference.elem).clone(),
other => other.clone(),
};
(quote!(#params), quote!(#called(cx, params)))
}
_ => (quote!(()), quote!(#called(cx))),
};
quote! {
#function
impl #impl_generics #feature::Feature for #installed #where_clause {
type Params = #params;
type Exports = <#installed as #feature::Manifest>::Exports;
const DECLARED: ::core::option::Option<#gc::actor::shape::Declared> =
::core::option::Option::Some(#gc::actor::shape::Declared {
file: ::core::file!(),
line: ::core::line!(),
column: ::core::column!(),
crate_dir: ::core::env!("CARGO_MANIFEST_DIR"),
});
fn install(
cx: &#feature::FeatureInitContext,
params: &Self::Params,
) -> #gc::__private::anyhow::Result<Self> {
let _ = params;
#call
}
}
}
.into()
}
fn installed(function: &ItemFn) -> Option<Type> {
let ReturnType::Type(_, returned) = &function.sig.output else {
return None;
};
let Type::Path(path) = &**returned else {
return None;
};
let last = path.path.segments.last()?;
if last.ident != "Result" {
return None;
}
let PathArguments::AngleBracketed(arguments) = &last.arguments else {
return None;
};
arguments.args.iter().find_map(|argument| match argument {
GenericArgument::Type(ty) => Some(ty.clone()),
_ => None,
})
}