diff options
author | HampusM <hampus@hampusmat.com> | 2022-07-09 19:57:10 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2022-07-09 19:57:10 +0200 |
commit | 15d7309072658800339eaba0491ba87ba6e266b2 (patch) | |
tree | f3bdfef0fb5f24e27e0f6803febc58879b6323dd /syrette_macros/src/lib.rs | |
parent | fe4cbae692b873425db562784657daeaa7914772 (diff) |
feat: add DI container
Diffstat (limited to 'syrette_macros/src/lib.rs')
-rw-r--r-- | syrette_macros/src/lib.rs | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/syrette_macros/src/lib.rs b/syrette_macros/src/lib.rs index e69de29..3fd8a59 100644 --- a/syrette_macros/src/lib.rs +++ b/syrette_macros/src/lib.rs @@ -0,0 +1,221 @@ +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse, parse_macro_input, parse_str, punctuated::Punctuated, token::Comma, + AttributeArgs, FnArg, GenericArgument, ImplItem, ItemImpl, Meta, NestedMeta, Path, + PathArguments, Type, TypePath, +}; + +mod libs; + +use libs::intertrait_macros::{ + args::{Casts, Flag, Targets}, + gen_caster::generate_caster, +}; + +const NO_INTERFACE_ARG_ERR_MESSAGE: &str = + "Expected a argument specifying a interface trait"; + +const INVALID_ARG_ERR_MESSAGE: &str = "Invalid argument passed"; + +const INVALID_ITEM_TYPE_ERR_MESSAGE: &str = + "The attached to item is not a trait implementation"; + +const IMPL_NO_NEW_METHOD_ERR_MESSAGE: &str = + "The attached to trait implementation is missing a new method"; + +const IMPL_NEW_METHOD_SELF_PARAM_ERR_MESSAGE: &str = + "The new method of the attached to trait implementation cannot have a self parameter"; + +const IMPL_NEW_METHOD_BOX_PARAMS_ERR_MESSAGE: &str = + "All parameters of the new method of the attached to trait implementation must be std::boxed::Box"; + +fn path_to_string(path: &Path) -> String +{ + return path + .segments + .pairs() + .fold(String::new(), |mut acc, segment_pair| { + let segment_ident = &segment_pair.value().ident; + + acc.push_str(segment_ident.to_string().as_str()); + + let opt_colon_two = segment_pair.punct(); + + match opt_colon_two { + Some(colon_two) => { + acc.push_str(colon_two.to_token_stream().to_string().as_str()) + } + None => {} + } + + acc + }); +} + +fn get_fn_args_has_self(fn_args: &Punctuated<FnArg, Comma>) -> bool +{ + return fn_args.iter().any(|arg| match arg { + FnArg::Receiver(_) => true, + &_ => false, + }); +} + +fn get_fn_arg_type_paths(fn_args: &Punctuated<FnArg, Comma>) -> Vec<TypePath> +{ + return fn_args.iter().fold(Vec::<TypePath>::new(), |mut acc, arg| { + match arg { + FnArg::Typed(typed_fn_arg) => match typed_fn_arg.ty.as_ref() { + Type::Path(arg_type_path) => acc.push(arg_type_path.clone()), + &_ => {} + }, + FnArg::Receiver(_receiver_fn_arg) => {} + } + + acc + }); +} + +fn get_dependency_types(item_impl: &ItemImpl) -> Vec<Type> +{ + let impl_items = &item_impl.items; + + let opt_new_method_impl_item = impl_items.iter().find(|item| match item { + ImplItem::Method(method_item) => method_item.sig.ident == "new", + &_ => false, + }); + + let new_method_impl_item = match opt_new_method_impl_item { + Some(item) => match item { + ImplItem::Method(method_item) => method_item, + &_ => panic!("{}", IMPL_NO_NEW_METHOD_ERR_MESSAGE), + }, + None => panic!("{}", IMPL_NO_NEW_METHOD_ERR_MESSAGE), + }; + + let new_method_inputs = &new_method_impl_item.sig.inputs; + + if get_fn_args_has_self(new_method_inputs) { + panic!("{}", IMPL_NEW_METHOD_SELF_PARAM_ERR_MESSAGE) + } + + let new_method_arg_type_paths = get_fn_arg_type_paths(new_method_inputs); + + return new_method_arg_type_paths.iter().fold( + Vec::<Type>::new(), + |mut acc, arg_type_path| { + let arg_type_path_string = path_to_string(&arg_type_path.path); + + if arg_type_path_string != "Box" + && arg_type_path_string != "std::boxed::Box" + && arg_type_path_string != "boxed::Box" + { + panic!("{}", IMPL_NEW_METHOD_BOX_PARAMS_ERR_MESSAGE); + } + + // Assume the type path has a last segment. + // The Box check wouldn't pass if it didn't + let last_path_segment = arg_type_path.path.segments.last().unwrap(); + + match &last_path_segment.arguments { + PathArguments::AngleBracketed(angle_bracketed_generic_args) => { + let generic_args = &angle_bracketed_generic_args.args; + + let opt_first_generic_arg = generic_args.first(); + + // Assume a first generic argument exists because Box requires one + let first_generic_arg = opt_first_generic_arg.as_ref().unwrap(); + + match first_generic_arg { + GenericArgument::Type(first_generic_arg_type) => { + acc.push(first_generic_arg_type.clone()); + } + &_ => {} + } + } + &_ => {} + } + + acc + }, + ); +} + +#[proc_macro_attribute] +pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenStream +{ + let args = parse_macro_input!(args_stream as AttributeArgs); + + if args.is_empty() { + panic!("{}", NO_INTERFACE_ARG_ERR_MESSAGE); + } + + if args.len() > 1 { + panic!("Only a single argument is expected"); + } + + let interface_path = match &args[0] { + NestedMeta::Meta(arg_meta) => match arg_meta { + Meta::Path(path_arg) => path_arg, + &_ => panic!("{}", INVALID_ARG_ERR_MESSAGE), + }, + &_ => panic!("{}", INVALID_ARG_ERR_MESSAGE), + }; + + let item_impl: ItemImpl = match parse(impl_stream) { + Ok(impl_parsed) => impl_parsed, + Err(_) => { + panic!("{}", INVALID_ITEM_TYPE_ERR_MESSAGE) + } + }; + + let self_type = item_impl.self_ty.as_ref(); + + let self_type_path = match self_type { + Type::Path(path_self_type) => path_self_type.path.clone(), + &_ => parse_str("invalid_type").unwrap(), + }; + + let dependency_types = get_dependency_types(&item_impl); + + quote! { + #item_impl + + impl syrette::injectable::Injectable for #self_type_path { + fn resolve( + di_container: &syrette::DIContainer + ) -> error_stack::Result<Box<Self>, syrette::injectable::ResolveError> + { + use error_stack::ResultExt; + + return Ok(Box::new(Self::new( + #(di_container.get::<#dependency_types>() + .change_context(syrette::injectable::ResolveError) + .attach_printable(format!( + "Unable to resolve a dependency of {}", + std::any::type_name::<#self_type_path>() + ))?, + )* + ))); + } + } + + syrette::castable_to!(#self_type_path => #interface_path); + } + .into() +} + +#[proc_macro] +pub fn castable_to(input: TokenStream) -> TokenStream +{ + let Casts { + ty, + targets: Targets { flags, paths }, + } = parse_macro_input!(input); + + paths + .iter() + .map(|t| generate_caster(&ty, t, flags.contains(&Flag::Sync))) + .collect::<proc_macro2::TokenStream>() + .into() +} |