diff options
author | HampusM <hampus@hampusmat.com> | 2022-07-17 14:55:41 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2022-07-17 14:55:41 +0200 |
commit | 51e8d04c2299e6468213d8ee4f9e15d783094379 (patch) | |
tree | 77dd9436588a1fc8a15fbf7e8da7b8e7068bd0b1 /syrette_macros | |
parent | e906a0fc5922776b8ecd17e3100b24fc44cedf19 (diff) |
refactor: reorganize and improve macros
Diffstat (limited to 'syrette_macros')
-rw-r--r-- | syrette_macros/src/factory_type_alias.rs | 83 | ||||
-rw-r--r-- | syrette_macros/src/injectable_impl.rs | 244 | ||||
-rw-r--r-- | syrette_macros/src/injectable_macro_args.rs | 17 | ||||
-rw-r--r-- | syrette_macros/src/lib.rs | 385 |
4 files changed, 430 insertions, 299 deletions
diff --git a/syrette_macros/src/factory_type_alias.rs b/syrette_macros/src/factory_type_alias.rs new file mode 100644 index 0000000..82e2315 --- /dev/null +++ b/syrette_macros/src/factory_type_alias.rs @@ -0,0 +1,83 @@ +use syn::parse::{Parse, ParseStream}; +use syn::{GenericArgument, ItemType, Path, Type, TypeParamBound, TypeTuple}; + +pub struct FactoryTypeAlias +{ + pub type_alias: ItemType, + pub factory_interface: Path, + pub arg_types: TypeTuple, + pub return_type: Type, +} + +impl Parse for FactoryTypeAlias +{ + fn parse(input: ParseStream) -> syn::Result<Self> + { + let type_alias = match input.parse::<ItemType>() { + Ok(type_alias) => Ok(type_alias), + Err(_) => Err(input.error("Expected a type alias")), + }?; + + let aliased_trait = match &type_alias.ty.as_ref() { + Type::TraitObject(alias_type) => Ok(alias_type), + &_ => Err(input.error("Expected the aliased type to be a trait")), + }?; + + if aliased_trait.bounds.len() != 1 { + return Err(input.error("Expected the aliased trait to have a single bound.")); + } + + let bound_path = &match aliased_trait.bounds.first().unwrap() { + TypeParamBound::Trait(trait_bound) => Ok(trait_bound), + &_ => { + Err(input.error("Expected the bound of the aliased trait to be a trait")) + } + }? + .path; + + if bound_path.segments.is_empty() + || bound_path.segments.last().unwrap().ident != "IFactory" + { + return Err(input + .error("Expected the bound of the aliased trait to be 'dyn IFactory'")); + } + + let angle_bracketed_args = match &bound_path.segments.last().unwrap().arguments { + syn::PathArguments::AngleBracketed(angle_bracketed_args) => { + Ok(angle_bracketed_args) + } + &_ => { + Err(input.error("Expected angle bracketed arguments for 'dyn IFactory'")) + } + }?; + + let arg_types = match &angle_bracketed_args.args[0] { + GenericArgument::Type(arg_types_type) => match arg_types_type { + Type::Tuple(arg_types) => Ok(arg_types), + &_ => Err(input.error(concat!( + "Expected the first angle bracketed argument ", + "of 'dyn IFactory' to be a type tuple" + ))), + }, + &_ => Err(input.error(concat!( + "Expected the first angle bracketed argument ", + "of 'dyn IFactory' to be a type" + ))), + }?; + + let return_type = match &angle_bracketed_args.args[1] { + GenericArgument::Type(arg_type) => Ok(arg_type), + &_ => Err(input.error(concat!( + "Expected the second angle bracketed argument ", + "of 'dyn IFactory' to be a type" + ))), + }?; + + Ok(Self { + type_alias: type_alias.clone(), + factory_interface: bound_path.clone(), + arg_types: arg_types.clone(), + return_type: return_type.clone(), + }) + } +} diff --git a/syrette_macros/src/injectable_impl.rs b/syrette_macros/src/injectable_impl.rs new file mode 100644 index 0000000..e7d1b54 --- /dev/null +++ b/syrette_macros/src/injectable_impl.rs @@ -0,0 +1,244 @@ +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::{ + parse_str, punctuated::Punctuated, token::Comma, ExprMethodCall, FnArg, + GenericArgument, Ident, ImplItem, ImplItemMethod, ItemImpl, Path, PathArguments, + Type, TypePath, +}; + +const DI_CONTAINER_VAR_NAME: &str = "di_container"; + +pub struct InjectableImpl +{ + pub dependency_types: Vec<Type>, + pub self_type: Type, + pub original_impl: ItemImpl, +} + +impl Parse for InjectableImpl +{ + fn parse(input: ParseStream) -> syn::Result<Self> + { + match input.parse::<ItemImpl>() { + Ok(impl_parsed_input) => { + match Self::_get_dependency_types(&impl_parsed_input) { + Ok(dependency_types) => Ok(Self { + dependency_types, + self_type: impl_parsed_input.self_ty.as_ref().clone(), + original_impl: impl_parsed_input, + }), + Err(error_msg) => Err(input.error(error_msg)), + } + } + Err(_) => Err(input.error("Expected an impl")), + } + } +} + +impl InjectableImpl +{ + pub fn expand(&self) -> proc_macro2::TokenStream + { + let original_impl = &self.original_impl; + let self_type = &self.self_type; + + let di_container_var: Ident = parse_str(DI_CONTAINER_VAR_NAME).unwrap(); + + let get_dependencies = Self::_create_get_dependencies(&self.dependency_types); + + quote! { + #original_impl + + impl syrette::interfaces::injectable::Injectable for #self_type { + fn resolve( + #di_container_var: &syrette::DIContainer + ) -> error_stack::Result< + syrette::ptr::InterfacePtr<Self>, + syrette::errors::injectable::ResolveError> + { + use error_stack::ResultExt; + + return Ok(syrette::ptr::InterfacePtr::new(Self::new( + #(#get_dependencies + .change_context(syrette::errors::injectable::ResolveError) + .attach_printable( + format!( + "Unable to resolve a dependency of {}", + std::any::type_name::<#self_type>() + ) + )? + ),* + ))); + } + } + } + } + + fn _create_get_dependencies(dependency_types: &[Type]) -> Vec<ExprMethodCall> + { + dependency_types + .iter() + .filter_map(|dep_type| match dep_type { + Type::TraitObject(dep_type_trait) => Some( + parse_str( + format!( + "{}.get::<{}>()", + DI_CONTAINER_VAR_NAME, + dep_type_trait.to_token_stream() + ) + .as_str(), + ) + .unwrap(), + ), + Type::Path(dep_type_path) => { + let dep_type_path_str = Self::_path_to_string(&dep_type_path.path); + + let get_method_name = if dep_type_path_str.ends_with("Factory") { + "get_factory" + } else { + "get" + }; + + Some( + parse_str( + format!( + "{}.{}::<{}>()", + DI_CONTAINER_VAR_NAME, get_method_name, dep_type_path_str + ) + .as_str(), + ) + .unwrap(), + ) + } + &_ => None, + }) + .collect() + } + + fn _find_method_by_name<'impl_lt>( + item_impl: &'impl_lt ItemImpl, + method_name: &'static str, + ) -> Option<&'impl_lt ImplItemMethod> + { + let impl_items = &item_impl.items; + + impl_items + .iter() + .filter_map(|impl_item| match impl_item { + ImplItem::Method(method_item) => Some(method_item), + &_ => None, + }) + .find(|method_item| method_item.sig.ident == method_name) + } + + fn get_has_fn_args_self(fn_args: &Punctuated<FnArg, Comma>) -> bool + { + fn_args.iter().any(|arg| match arg { + FnArg::Receiver(_) => true, + &_ => false, + }) + } + + fn _get_fn_arg_type_paths(fn_args: &Punctuated<FnArg, Comma>) -> Vec<&TypePath> + { + fn_args + .iter() + .filter_map(|arg| match arg { + FnArg::Typed(typed_fn_arg) => match typed_fn_arg.ty.as_ref() { + Type::Path(arg_type_path) => Some(arg_type_path), + Type::Reference(ref_type_path) => match ref_type_path.elem.as_ref() { + Type::Path(arg_type_path) => Some(arg_type_path), + &_ => None, + }, + &_ => None, + }, + FnArg::Receiver(_receiver_fn_arg) => None, + }) + .collect() + } + + fn _path_to_string(path: &Path) -> String + { + 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 _is_type_path_ptr(type_path: &TypePath) -> bool + { + let arg_type_path_string = Self::_path_to_string(&type_path.path); + + arg_type_path_string == "InterfacePtr" + || arg_type_path_string == "ptr::InterfacePtr" + || arg_type_path_string == "syrrete::ptr::InterfacePtr" + || arg_type_path_string == "FactoryPtr" + || arg_type_path_string == "ptr::FactoryPtr" + || arg_type_path_string == "syrrete::ptr::FactoryPtr" + } + + fn _get_dependency_types(item_impl: &ItemImpl) -> Result<Vec<Type>, &'static str> + { + let new_method_impl_item = match Self::_find_method_by_name(item_impl, "new") { + Some(method_item) => Ok(method_item), + None => Err("Missing a 'new' method"), + }?; + + let new_method_args = &new_method_impl_item.sig.inputs; + + if Self::get_has_fn_args_self(new_method_args) { + return Err("Unexpected self argument in 'new' method"); + } + + let new_method_arg_type_paths = Self::_get_fn_arg_type_paths(new_method_args); + + if new_method_arg_type_paths + .iter() + .any(|arg_type_path| !Self::_is_type_path_ptr(arg_type_path)) + { + return Err("All argument types in 'new' method must ptr types"); + } + + Ok(new_method_arg_type_paths + .iter() + .filter_map(|arg_type_path| { + // Assume the type path has a last segment. + 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 InterfacePtr and + // FactoryPtr requires one + let first_generic_arg = opt_first_generic_arg.as_ref().unwrap(); + + match first_generic_arg { + GenericArgument::Type(first_generic_arg_type) => { + Some(first_generic_arg_type.clone()) + } + &_ => None, + } + } + &_ => None, + } + }) + .collect()) + } +} diff --git a/syrette_macros/src/injectable_macro_args.rs b/syrette_macros/src/injectable_macro_args.rs new file mode 100644 index 0000000..4ef4389 --- /dev/null +++ b/syrette_macros/src/injectable_macro_args.rs @@ -0,0 +1,17 @@ +use syn::parse::{Parse, ParseStream}; +use syn::TypePath; + +pub struct InjectableMacroArgs +{ + pub interface: TypePath, +} + +impl Parse for InjectableMacroArgs +{ + fn parse(input: ParseStream) -> syn::Result<Self> + { + Ok(Self { + interface: input.parse()?, + }) + } +} diff --git a/syrette_macros/src/lib.rs b/syrette_macros/src/lib.rs index 91a0562..3145b5f 100644 --- a/syrette_macros/src/lib.rs +++ b/syrette_macros/src/lib.rs @@ -1,162 +1,21 @@ use proc_macro::TokenStream; -use quote::{quote, ToTokens}; -use syn::{ - parse, parse_macro_input, parse_str, punctuated::Punctuated, token::Comma, - AttributeArgs, ExprMethodCall, FnArg, GenericArgument, ImplItem, ItemImpl, ItemType, - Meta, NestedMeta, Path, PathArguments, Type, TypeParamBound, TypePath, -}; +use quote::quote; +use syn::{parse, parse_macro_input}; +mod factory_type_alias; +mod injectable_impl; +mod injectable_macro_args; mod libs; +use factory_type_alias::FactoryTypeAlias; +use injectable_impl::InjectableImpl; +use injectable_macro_args::InjectableMacroArgs; 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_PARAM_TYPES_ERR_MESSAGE: &str = concat!( - "All parameters of the new method of the attached to trait implementation ", - "must be either syrette::ptr::InterfacePtr or syrrete::ptr::FactoryPtr (for factories)" -); - -const INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE: &str = - "Invalid aliased trait. Must be 'dyn IFactory'"; - -const INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE: &str = - "Invalid arguments for 'dyn IFactory'"; - -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()), - Type::Reference(ref_type_path) => match ref_type_path.elem.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 != "InterfacePtr" - && arg_type_path_string != "ptr::InterfacePtr" - && arg_type_path_string != "syrrete::ptr::InterfacePtr" - && arg_type_path_string != "FactoryPtr" - && arg_type_path_string != "ptr::FactoryPtr" - && arg_type_path_string != "syrrete::ptr::FactoryPtr" - { - panic!("{}", IMPL_NEW_METHOD_PARAM_TYPES_ERR_MESSAGE); - } - - // Assume the type path has a last segment. - 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 InterfacePtr and - // FactoryPtr 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 - }, - ); -} - -/// Makes a struct injectable. Therefore usable with `DIContainer`. +/// Makes a struct injectable. Thereby usable with `DIContainer`. /// /// # Arguments /// @@ -166,15 +25,28 @@ fn get_dependency_types(item_impl: &ItemImpl) -> Vec<Type> /// ``` /// trait IConfigReader /// { -/// fn read_config() -> Config; +/// fn read_config(&self) -> Config; +/// } +/// +/// struct ConfigReader +/// { +/// _file_reader: InterfacePtr<IFileReader>, /// } /// -/// struct ConfigReader {} +/// impl ConfigReader +/// { +/// fn new(file_reader: InterfacePtr<IFileReader>) -> Self +/// { +/// Self { +/// _file_reader: file_reader +/// } +/// } +/// } /// /// #[injectable(IConfigReader)] /// impl IConfigReader for ConfigReader /// { -/// fn read_config() -> Config +/// fn read_config(&self) -> Config /// { /// // Stuff here /// } @@ -183,175 +55,90 @@ fn get_dependency_types(item_impl: &ItemImpl) -> Vec<Type> #[proc_macro_attribute] pub fn injectable(args_stream: TokenStream, impl_stream: TokenStream) -> TokenStream { - let args = parse_macro_input!(args_stream as AttributeArgs); + let InjectableMacroArgs { + interface: interface_type_path, + } = parse_macro_input!(args_stream); - if args.is_empty() { - panic!("{}", NO_INTERFACE_ARG_ERR_MESSAGE); - } + let injectable_impl: InjectableImpl = parse(impl_stream).unwrap(); - 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); + let expanded_injectable_impl = injectable_impl.expand(); - let get_dependencies = dependency_types.iter().fold( - Vec::<ExprMethodCall>::new(), - |mut acc, dep_type| { - match dep_type { - Type::TraitObject(dep_type_trait) => { - acc.push( - parse_str( - format!( - "di_container.get::<{}>()", - dep_type_trait.to_token_stream() - ) - .as_str(), - ) - .unwrap(), - ); - } - Type::Path(dep_type_path) => { - let dep_type_path_str = path_to_string(&dep_type_path.path); - - let get_method_name = if dep_type_path_str.ends_with("Factory") { - "get_factory" - } else { - "get" - }; - - acc.push( - parse_str( - format!( - "di_container.{}::<{}>()", - get_method_name, dep_type_path_str - ) - .as_str(), - ) - .unwrap(), - ); - } - &_ => {} - } - - acc - }, - ); + let self_type = &injectable_impl.self_type; quote! { - #item_impl + #expanded_injectable_impl - impl syrette::interfaces::injectable::Injectable for #self_type_path { - fn resolve( - di_container: &syrette::DIContainer - ) -> error_stack::Result< - syrette::ptr::InterfacePtr<Self>, - syrette::errors::injectable::ResolveError> - { - use error_stack::ResultExt; - - return Ok(syrette::ptr::InterfacePtr::new(Self::new( - #(#get_dependencies - .change_context(syrette::errors::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); + syrette::castable_to!(#self_type => #interface_type_path); } .into() } +/// Makes a type alias usable as a factory interface. +/// +/// # Examples +/// ``` +/// trait IUser +/// { +/// fn name(&self) -> String; +/// fn age(&self) -> i32; +/// } +/// +/// struct User +/// { +/// _name: String, +/// _age: i32, +/// } +/// +/// impl User +/// { +/// fn new(name: String, age: i32) -> Self +/// { +/// Self { +/// _name: name, +/// _age: age, +/// } +/// } +/// } +/// +/// impl IUser for User +/// { +/// fn name(&self) -> String +/// { +/// self._name +/// } +/// +/// fn age(&self) -> i32 +/// { +/// self._age +/// } +/// } +/// +/// type UserFactory = dyn IFactory<(String, i32), dyn IUser>; +/// ``` #[proc_macro_attribute] pub fn factory(_: TokenStream, type_alias_stream: TokenStream) -> TokenStream { - let type_alias: ItemType = parse(type_alias_stream).unwrap(); - - let aliased_trait = match &type_alias.ty.as_ref() { - Type::TraitObject(alias_type) => alias_type, - &_ => panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE), - }; - - if aliased_trait.bounds.len() != 1 { - panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE); - } - - let type_bound = aliased_trait.bounds.first().unwrap(); - - let trait_bound = match type_bound { - TypeParamBound::Trait(trait_bound) => trait_bound, - &_ => panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE), - }; - - let trait_bound_path = &trait_bound.path; - - if trait_bound_path.segments.is_empty() - || trait_bound_path.segments.last().unwrap().ident != "IFactory" - { - panic!("{}", INVALID_ALIASED_FACTORY_TRAIT_ERR_MESSAGE); - } - - let factory_path_segment = trait_bound_path.segments.last().unwrap(); - - let factory_path_segment_args = &match &factory_path_segment.arguments { - syn::PathArguments::AngleBracketed(args) => args, - &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), - } - .args; - - let factory_arg_types_type = match &factory_path_segment_args[0] { - GenericArgument::Type(arg_type) => arg_type, - &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), - }; - - let factory_return_type = match &factory_path_segment_args[1] { - GenericArgument::Type(arg_type) => arg_type, - &_ => panic!("{}", INVALID_ALIASED_FACTORY_ARGS_ERR_MESSAGE), - }; + let FactoryTypeAlias { + type_alias, + factory_interface, + arg_types, + return_type, + } = parse(type_alias_stream).unwrap(); quote! { #type_alias syrette::castable_to!( syrette::castable_factory::CastableFactory< - #factory_arg_types_type, - #factory_return_type - > => #trait_bound_path + #arg_types, + #return_type + > => #factory_interface ); syrette::castable_to!( syrette::castable_factory::CastableFactory< - #factory_arg_types_type, - #factory_return_type + #arg_types, + #return_type > => syrette::castable_factory::AnyFactory ); } |