aboutsummaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-08-27 23:41:41 +0200
committerHampusM <hampus@hampusmat.com>2022-08-27 23:41:41 +0200
commite0f90a8e384615c79d7d51c66d19294d75e79391 (patch)
treef3df3d1cd92f7d4a978feaa5a9a5f773dd0901ee /macros/src
parentd4078c84a83d121a4e3492955359cedb3b404476 (diff)
feat: implement named bindings
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/dependency.rs81
-rw-r--r--macros/src/dependency_type.rs40
-rw-r--r--macros/src/injectable_impl.rs200
-rw-r--r--macros/src/lib.rs107
-rw-r--r--macros/src/named_attr_input.rs21
-rw-r--r--macros/src/util/item_impl.rs12
-rw-r--r--macros/src/util/mod.rs1
-rw-r--r--macros/src/util/syn_path.rs22
8 files changed, 304 insertions, 180 deletions
diff --git a/macros/src/dependency.rs b/macros/src/dependency.rs
new file mode 100644
index 0000000..d20af90
--- /dev/null
+++ b/macros/src/dependency.rs
@@ -0,0 +1,81 @@
+use std::error::Error;
+
+use proc_macro2::Ident;
+use syn::{parse2, FnArg, GenericArgument, LitStr, PathArguments, Type};
+
+use crate::named_attr_input::NamedAttrInput;
+use crate::util::syn_path::syn_path_to_string;
+
+pub struct Dependency
+{
+ pub interface: Type,
+ pub ptr: Ident,
+ pub name: Option<LitStr>,
+}
+
+impl Dependency
+{
+ pub fn build(new_method_arg: &FnArg) -> Result<Self, Box<dyn Error>>
+ {
+ let typed_new_method_arg = match new_method_arg {
+ FnArg::Typed(typed_arg) => Ok(typed_arg),
+ FnArg::Receiver(_) => Err("Unexpected self argument in 'new' method"),
+ }?;
+
+ let ptr_type_path = match typed_new_method_arg.ty.as_ref() {
+ Type::Path(arg_type_path) => Ok(arg_type_path),
+ Type::Reference(ref_type_path) => match ref_type_path.elem.as_ref() {
+ Type::Path(arg_type_path) => Ok(arg_type_path),
+ &_ => Err("Unexpected reference to non-path type"),
+ },
+ &_ => Err("Expected a path or a reference type"),
+ }?;
+
+ let ptr_path_segment = ptr_type_path.path.segments.last().map_or_else(
+ || Err("Expected pointer type path to have a last segment"),
+ Ok,
+ )?;
+
+ let ptr = ptr_path_segment.ident.clone();
+
+ let ptr_path_generic_args = &match &ptr_path_segment.arguments {
+ PathArguments::AngleBracketed(generic_args) => Ok(generic_args),
+ &_ => Err("Expected pointer type to have a generic type argument"),
+ }?
+ .args;
+
+ let interface = if let Some(GenericArgument::Type(interface)) =
+ ptr_path_generic_args.first()
+ {
+ Ok(interface.clone())
+ } else {
+ Err("Expected pointer type to have a generic type argument")
+ }?;
+
+ let arg_attrs = &typed_new_method_arg.attrs;
+
+ let opt_named_attr = arg_attrs.iter().find(|attr| {
+ attr.path.get_ident().map_or_else(
+ || false,
+ |attr_ident| attr_ident.to_string().as_str() == "named",
+ ) || syn_path_to_string(&attr.path) == "syrette::named"
+ });
+
+ let opt_named_attr_tokens = opt_named_attr.map(|attr| &attr.tokens);
+
+ let opt_named_attr_input =
+ if let Some(named_attr_tokens) = opt_named_attr_tokens {
+ Some(parse2::<NamedAttrInput>(named_attr_tokens.clone()).map_err(
+ |err| format!("Invalid input for 'named' attribute. {}", err),
+ )?)
+ } else {
+ None
+ };
+
+ Ok(Self {
+ interface,
+ ptr,
+ name: opt_named_attr_input.map(|named_attr_input| named_attr_input.name),
+ })
+ }
+}
diff --git a/macros/src/dependency_type.rs b/macros/src/dependency_type.rs
deleted file mode 100644
index 35f810e..0000000
--- a/macros/src/dependency_type.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use proc_macro2::Ident;
-use syn::{GenericArgument, PathArguments, Type, TypePath};
-
-pub struct DependencyType
-{
- pub interface: Type,
- pub ptr: Ident,
-}
-
-impl DependencyType
-{
- pub fn from_type_path(type_path: &TypePath) -> Option<Self>
- {
- // Assume the type path has a last segment.
- let last_path_segment = type_path.path.segments.last().unwrap();
-
- let ptr = &last_path_segment.ident;
-
- 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 TransientPtr,
- // SingletonPtr 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(Self {
- interface: first_generic_arg_type.clone(),
- ptr: ptr.clone(),
- }),
- &_ => None,
- }
- }
- &_ => None,
- }
- }
-}
diff --git a/macros/src/injectable_impl.rs b/macros/src/injectable_impl.rs
index d74acb3..6edcab3 100644
--- a/macros/src/injectable_impl.rs
+++ b/macros/src/injectable_impl.rs
@@ -1,20 +1,20 @@
+use std::error::Error;
+
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::Generics;
-use syn::{
- parse_str, punctuated::Punctuated, token::Comma, ExprMethodCall, FnArg, ItemImpl,
- Path, Type, TypePath,
-};
+use syn::{parse_str, ExprMethodCall, FnArg, ItemImpl, Type};
-use crate::dependency_type::DependencyType;
-use crate::util::item_impl::find_impl_method_by_name;
+use crate::dependency::Dependency;
+use crate::util::item_impl::find_impl_method_by_name_mut;
+use crate::util::syn_path::syn_path_to_string;
const DI_CONTAINER_VAR_NAME: &str = "di_container";
const DEPENDENCY_HISTORY_VAR_NAME: &str = "dependency_history";
pub struct InjectableImpl
{
- pub dependency_types: Vec<DependencyType>,
+ pub dependencies: Vec<Dependency>,
pub self_type: Type,
pub generics: Generics,
pub original_impl: ItemImpl,
@@ -24,13 +24,13 @@ impl Parse for InjectableImpl
{
fn parse(input: ParseStream) -> syn::Result<Self>
{
- let impl_parsed_input = input.parse::<ItemImpl>()?;
+ let mut impl_parsed_input = input.parse::<ItemImpl>()?;
- let dependency_types = Self::get_dependency_types(&impl_parsed_input)
+ let dependencies = Self::build_dependencies(&mut impl_parsed_input)
.map_err(|err| input.error(err))?;
Ok(Self {
- dependency_types,
+ dependencies,
self_type: impl_parsed_input.self_ty.as_ref().clone(),
generics: impl_parsed_input.generics.clone(),
original_impl: impl_parsed_input,
@@ -43,7 +43,7 @@ impl InjectableImpl
pub fn expand(&self, no_doc_hidden: bool) -> proc_macro2::TokenStream
{
let Self {
- dependency_types,
+ dependencies,
self_type,
generics,
original_impl,
@@ -52,7 +52,7 @@ impl InjectableImpl
let di_container_var = format_ident!("{}", DI_CONTAINER_VAR_NAME);
let dependency_history_var = format_ident!("{}", DEPENDENCY_HISTORY_VAR_NAME);
- let get_dep_method_calls = Self::create_get_dep_method_calls(dependency_types);
+ let get_dep_method_calls = Self::create_get_dep_method_calls(dependencies);
let maybe_doc_hidden = if no_doc_hidden {
quote! {}
@@ -111,46 +111,38 @@ impl InjectableImpl
}
fn create_get_dep_method_calls(
- dependency_types: &[DependencyType],
+ dependencies: &[Dependency],
) -> Vec<proc_macro2::TokenStream>
{
- dependency_types
+ dependencies
.iter()
- .filter_map(|dep_type| match &dep_type.interface {
- Type::TraitObject(dep_type_trait) => {
- let method_call = parse_str::<ExprMethodCall>(
- format!(
- "{}.get_bound::<{}>({}.clone())",
- DI_CONTAINER_VAR_NAME,
- dep_type_trait.to_token_stream(),
- DEPENDENCY_HISTORY_VAR_NAME
- )
- .as_str(),
- )
- .ok()?;
-
- Some((method_call, dep_type))
-
- /*
- */
- }
- Type::Path(dep_type_path) => {
- let dep_type_path_str = Self::path_to_string(&dep_type_path.path);
-
- let method_call: ExprMethodCall = parse_str(
- format!(
- "{}.get_bound::<{}>({}.clone())",
- DI_CONTAINER_VAR_NAME,
- dep_type_path_str,
- DEPENDENCY_HISTORY_VAR_NAME
+ .filter_map(|dependency| {
+ let dep_interface_str = match &dependency.interface {
+ Type::TraitObject(interface_trait) => {
+ Some(interface_trait.to_token_stream().to_string())
+ }
+ Type::Path(path_interface) => {
+ Some(syn_path_to_string(&path_interface.path))
+ }
+ &_ => None,
+ }?;
+
+ let method_call = parse_str::<ExprMethodCall>(
+ format!(
+ "{}.get_bound::<{}>({}.clone(), {})",
+ DI_CONTAINER_VAR_NAME,
+ dep_interface_str,
+ DEPENDENCY_HISTORY_VAR_NAME,
+ dependency.name.as_ref().map_or_else(
+ || "None".to_string(),
+ |name| format!("Some(\"{}\")", name.value())
)
- .as_str(),
)
- .ok()?;
+ .as_str(),
+ )
+ .ok()?;
- Some((method_call, dep_type))
- }
- &_ => None,
+ Some((method_call, dependency))
})
.map(|(method_call, dep_type)| {
let ptr_name = dep_type.ptr.to_string();
@@ -168,95 +160,47 @@ impl InjectableImpl
.collect()
}
- #[allow(clippy::match_wildcard_for_single_variants)]
- 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
+ fn build_dependencies(
+ item_impl: &mut ItemImpl,
+ ) -> Result<Vec<Dependency>, Box<dyn Error>>
{
- let arg_type_path_string = Self::path_to_string(&type_path.path);
-
- arg_type_path_string == "TransientPtr"
- || arg_type_path_string == "ptr::TransientPtr"
- || arg_type_path_string == "syrrete::ptr::TransientPtr"
- || arg_type_path_string == "SingletonPtr"
- || arg_type_path_string == "ptr::SingletonPtr"
- || arg_type_path_string == "syrrete::ptr::SingletonPtr"
- || 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<DependencyType>, &'static str>
- {
- let new_method_impl_item = find_impl_method_by_name(item_impl, "new")
+ let new_method_impl_item = find_impl_method_by_name_mut(item_impl, "new")
.map_or_else(|| Err("Missing a 'new' method"), Ok)?;
- let new_method_args = &new_method_impl_item.sig.inputs;
+ let new_method_args = &mut new_method_impl_item.sig.inputs;
+
+ let dependencies: Result<Vec<_>, _> =
+ new_method_args.iter().map(Dependency::build).collect();
+
+ for arg in new_method_args {
+ let typed_arg = if let FnArg::Typed(typed_arg) = arg {
+ typed_arg
+ } else {
+ continue;
+ };
+
+ let attrs_to_remove: Vec<_> = typed_arg
+ .attrs
+ .iter()
+ .enumerate()
+ .filter_map(|(index, attr)| {
+ if syn_path_to_string(&attr.path).as_str() == "syrette::named" {
+ return Some(index);
+ }
- if Self::get_has_fn_args_self(new_method_args) {
- return Err("Unexpected self argument in 'new' method");
- }
+ if attr.path.get_ident()?.to_string().as_str() == "named" {
+ return Some(index);
+ }
- let new_method_arg_type_paths = Self::get_fn_arg_type_paths(new_method_args);
+ None
+ })
+ .collect();
- 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");
+ for attr_index in attrs_to_remove {
+ typed_arg.attrs.remove(attr_index);
+ }
}
- Ok(new_method_arg_type_paths
- .iter()
- .filter_map(|arg_type_path| DependencyType::from_type_path(arg_type_path))
- .collect())
+ dependencies
}
}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 9b97be6..c7157c8 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -9,11 +9,12 @@ use quote::quote;
use syn::{parse, parse_macro_input};
mod declare_interface_args;
-mod dependency_type;
+mod dependency;
mod factory_type_alias;
mod injectable_impl;
mod injectable_macro_args;
mod libs;
+mod named_attr_input;
mod util;
use declare_interface_args::DeclareInterfaceArgs;
@@ -38,15 +39,60 @@ use libs::intertrait_macros::gen_caster::generate_caster;
/// declare the interface with the [`declare_interface!`] macro or use
/// the [`di_container_bind`] macro to create a DI container binding.
///
-/// # Example
+/// # Attributes
+/// Attributes specific to impls with this attribute macro.
+///
+/// ### Named
+/// Used inside the `new` method before a dependency argument. Declares the name of the
+/// dependency. Should be given the name quoted inside parenthesis.
+///
+/// The [`macro@named`] ghost attribute macro can be used for intellisense and
+/// autocompletion for this attribute.
+///
+/// For example:
/// ```
-/// use syrette::injectable;
+/// # use syrette::ptr::TransientPtr;
+/// # use syrette::injectable;
+/// #
+/// # trait IArmor {}
+/// #
+/// # trait IKnight {}
+/// #
+/// # struct Knight
+/// # {
+/// # tough_armor: TransientPtr<dyn IArmor>,
+/// # light_armor: TransientPtr<dyn IArmor>,
+/// # }
+/// #
+/// #[injectable(IKnight)]
+/// impl Knight
+/// {
+/// pub fn new(
+/// #[named("tough")]
+/// tough_armor: TransientPtr<dyn IArmor>,
///
-/// struct PasswordManager {}
+/// #[named("light")]
+/// light_armor: TransientPtr<dyn IArmor>
+/// ) -> Self
+/// {
+/// Self { tough_armor, light_armor }
+/// }
+/// }
+/// #
+/// # impl IKnight for Knight {}
+/// ```
///
+/// # Example
+/// ```
+/// # use syrette::injectable;
+/// #
+/// # struct PasswordManager {}
+/// #
/// #[injectable]
-/// impl PasswordManager {
-/// pub fn new() -> Self {
+/// impl PasswordManager
+/// {
+/// pub fn new() -> Self
+/// {
/// Self {}
/// }
/// }
@@ -191,3 +237,52 @@ pub fn declare_interface(input: TokenStream) -> TokenStream
generate_caster(&implementation, &interface).into()
}
+
+/// Declares the name of a dependency.
+///
+/// This macro attribute doesn't actually do anything. It only exists for the
+/// convenience of having intellisense and autocompletion.
+/// You might as well just use `named` if you don't care about that.
+///
+/// Only means something inside a `new` method inside a impl with
+/// the [`macro@injectable`] macro attribute.
+///
+/// # Examples
+/// ```
+/// # use syrette::ptr::TransientPtr;
+/// # use syrette::injectable;
+/// #
+/// # trait INinja {}
+/// # trait IWeapon {}
+/// #
+/// # struct Ninja
+/// # {
+/// # strong_weapon: TransientPtr<dyn IWeapon>,
+/// # weak_weapon: TransientPtr<dyn IWeapon>,
+/// # }
+/// #
+/// #[injectable(INinja)]
+/// impl Ninja
+/// {
+/// pub fn new(
+/// #[syrette::named("strong")]
+/// strong_weapon: TransientPtr<dyn IWeapon>,
+///
+/// #[syrette::named("weak")]
+/// weak_weapon: TransientPtr<dyn IWeapon>,
+/// ) -> Self
+/// {
+/// Self {
+/// strong_weapon,
+/// weak_weapon,
+/// }
+/// }
+/// }
+/// #
+/// # impl INinja for Ninja {}
+/// ```
+#[proc_macro_attribute]
+pub fn named(_: TokenStream, _: TokenStream) -> TokenStream
+{
+ TokenStream::new()
+}
diff --git a/macros/src/named_attr_input.rs b/macros/src/named_attr_input.rs
new file mode 100644
index 0000000..5f7123c
--- /dev/null
+++ b/macros/src/named_attr_input.rs
@@ -0,0 +1,21 @@
+use syn::parse::Parse;
+use syn::{parenthesized, LitStr};
+
+pub struct NamedAttrInput
+{
+ pub name: LitStr,
+}
+
+impl Parse for NamedAttrInput
+{
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self>
+ {
+ let content;
+
+ parenthesized!(content in input);
+
+ Ok(Self {
+ name: content.parse()?,
+ })
+ }
+}
diff --git a/macros/src/util/item_impl.rs b/macros/src/util/item_impl.rs
index 271ae2f..4bd7492 100644
--- a/macros/src/util/item_impl.rs
+++ b/macros/src/util/item_impl.rs
@@ -1,13 +1,13 @@
use syn::{ImplItem, ImplItemMethod, ItemImpl};
-pub fn find_impl_method_by_name<'item_impl>(
- item_impl: &'item_impl ItemImpl,
+pub fn find_impl_method_by_name_mut<'item_impl>(
+ item_impl: &'item_impl mut ItemImpl,
method_name: &'static str,
-) -> Option<&'item_impl ImplItemMethod>
+) -> Option<&'item_impl mut ImplItemMethod>
{
- let impl_items = &item_impl.items;
+ let impl_items = &mut item_impl.items;
- impl_items.iter().find_map(|impl_item| match impl_item {
+ impl_items.iter_mut().find_map(|impl_item| match impl_item {
ImplItem::Method(method_item) => {
if method_item.sig.ident == method_name {
Some(method_item)
@@ -15,6 +15,6 @@ pub fn find_impl_method_by_name<'item_impl>(
None
}
}
- &_ => None,
+ &mut _ => None,
})
}
diff --git a/macros/src/util/mod.rs b/macros/src/util/mod.rs
index fc7b2c6..4f2a594 100644
--- a/macros/src/util/mod.rs
+++ b/macros/src/util/mod.rs
@@ -1,2 +1,3 @@
pub mod item_impl;
pub mod iterator_ext;
+pub mod syn_path;
diff --git a/macros/src/util/syn_path.rs b/macros/src/util/syn_path.rs
new file mode 100644
index 0000000..15653bf
--- /dev/null
+++ b/macros/src/util/syn_path.rs
@@ -0,0 +1,22 @@
+#![allow(clippy::module_name_repetitions)]
+use quote::ToTokens;
+use syn::punctuated::Pair;
+
+pub fn syn_path_to_string(path: &syn::Path) -> String
+{
+ path.segments
+ .pairs()
+ .map(Pair::into_tuple)
+ .map(|(segment, opt_punct)| {
+ let segment_ident = &segment.ident;
+
+ format!(
+ "{}{}",
+ segment_ident,
+ opt_punct.map_or_else(String::new, |punct| punct
+ .to_token_stream()
+ .to_string())
+ )
+ })
+ .collect()
+}